From 42c51ee66cf7eec6126783674c127d3ee636e3f9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 18:22:51 +0000 Subject: [PATCH 1/6] Start fix for #760, #762 Assisted-by: Claude Code:claude-opus-5-5 From 29735ea5a9ffe78b8c7b37534329b92dbd390705 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 18:34:24 +0000 Subject: [PATCH 2/6] Test that a Poetry/PDM lock renders once per scan Hosted scans rewrite poetry.lock and pdm.lock once per patched package, rendering and re-parsing the whole lock each time. Count the engine's whole-lock renders and require one per lock for a dozen patched packages. Both tests fail today with 12 renders. Refs #760, #762 Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-cli/src/commands/list.rs | 15 +- crates/socket-patch-cli/src/commands/mod.rs | 22 +- .../src/commands/scan/discovery.rs | 62 ++- .../src/commands/scan/hosted.rs | 54 +- .../src/commands/scan/policy.rs | 68 ++- .../src/commands/scan/rollout.rs | 20 +- .../src/commands/scan/rollout_args.rs | 2 - .../socket-patch-cli/src/commands/vendor.rs | 5 +- .../tests/apply/apply_network.rs | 10 +- .../apply/in_process_gem_config_warning.rs | 4 +- .../tests/cli/covgap_output.rs | 10 +- .../tests/cli/interactive_prompts_e2e.rs | 5 +- .../tests/cli_config_fallback.rs | 7 +- .../socket-patch-cli/tests/cli_get_silent.rs | 5 +- .../socket-patch-cli/tests/cli_parse_list.rs | 11 +- .../tests/cli_parse_rollback.rs | 6 +- .../socket-patch-cli/tests/cli_parse_scan.rs | 29 +- .../coverage_fix_apply_silent_mute_exit.rs | 4 +- .../tests/covgap_commands_scan_hosted.rs | 42 +- .../socket-patch-cli/tests/e2e_bun_lockb.rs | 5 +- crates/socket-patch-cli/tests/e2e_cargo.rs | 6 +- crates/socket-patch-cli/tests/e2e_gem.rs | 6 +- crates/socket-patch-cli/tests/e2e_maven.rs | 3 +- crates/socket-patch-cli/tests/e2e_npm.rs | 6 +- crates/socket-patch-cli/tests/e2e_nuget.rs | 6 +- crates/socket-patch-cli/tests/e2e_pypi.rs | 6 +- .../tests/e2e_redirect_gem_build.rs | 5 +- .../tests/e2e_redirect_yarn_berry_build.rs | 6 +- .../tests/e2e_safety_cargo_build.rs | 6 +- .../socket-patch-cli/tests/e2e_safety_pnpm.rs | 18 +- .../tests/e2e_socket_yml_policy.rs | 471 ++++++++++++++---- .../tests/e2e_vex_lockfile/common_selftest.rs | 6 +- .../tests/e2e_yarn4_pnpm_linker_build.rs | 16 +- .../tests/e2e_yarn4_workspaces_build.rs | 16 +- .../tests/get/get_edge_cases_e2e.rs | 12 +- .../tests/get/global_packages_e2e.rs | 5 +- .../tests/help_text_hygiene.rs | 31 +- .../tests/hosted_memory_engine.rs | 3 +- .../tests/hosted_memory_parity.rs | 183 +++++-- .../tests/hosted_memory_rollout.rs | 42 +- .../tests/in_process_get_hosted_ecosystems.rs | 12 +- .../tests/in_process_redirect.rs | 7 +- .../tests/in_process_redirect/vlt.rs | 10 +- .../tests/in_process_redirect_pdm.rs | 30 +- .../tests/in_process_redirect_pipenv.rs | 73 ++- .../tests/in_process_redirect_pnpm.rs | 11 +- .../tests/in_process_rollback_hosted.rs | 5 +- .../tests/repair_vendor_flavors_e2e/vlt.rs | 5 +- .../rollback/rollback_duality_invariants.rs | 3 +- .../tests/scan/covgap_ecosystem_dispatch.rs | 8 +- .../tests/scan/scan_invariants.rs | 20 +- .../tests/scan/scan_paths_e2e.rs | 12 +- .../tests/update/covgap_commands_update.rs | 8 +- .../tests/yarn_berry_common/mod.rs | 4 +- crates/socket-patch-core/src/api/ranking.rs | 17 +- .../src/crawlers/python_crawler.rs | 10 +- .../src/formats/cargo/mod.rs | 12 +- .../src/formats/composer/mod.rs | 3 - .../src/formats/gem/gemfile.rs | 10 +- .../src/formats/gem/hosted.rs | 1 - .../socket-patch-core/src/formats/gem/mod.rs | 2 - crates/socket-patch-core/src/formats/mod.rs | 8 +- .../socket-patch-core/src/formats/pnpm/mod.rs | 76 ++- .../socket-patch-core/src/formats/registry.rs | 18 +- .../socket-patch-core/src/formats/yarn/mod.rs | 5 +- .../socket-patch-core/src/hosted/guidance.rs | 10 +- .../src/hosted/memory/discover.rs | 4 +- .../src/hosted/memory/limits.rs | 12 +- .../src/hosted/memory/mod.rs | 89 ++-- .../src/hosted/memory/roots.rs | 22 +- .../src/hosted/memory/select.rs | 14 +- .../src/hosted/memory/types.rs | 4 +- crates/socket-patch-core/src/ledgers.rs | 1 - crates/socket-patch-core/src/lib.rs | 1 - .../socket-patch-core/src/manifest/records.rs | 5 +- .../redirect/cargo_lock_equivalence_tests.rs | 4 +- .../redirect/golang_equivalence_tests.rs | 6 +- .../src/patch/redirect/mod.rs | 209 +++++--- .../src/patch/redirect/npmrc.rs | 3 - .../src/patch/redirect/pdm.rs | 73 ++- .../src/patch/redirect/pipenv.rs | 45 +- .../src/patch/redirect/poetry.rs | 48 +- .../src/patch/redirect/state.rs | 2 - .../src/patch/redirect/upstream/bun_lockb.rs | 5 +- .../src/patch/redirect/upstream/cargo.rs | 39 +- .../src/patch/redirect/upstream/gem.rs | 23 +- .../src/patch/redirect/upstream/golang.rs | 9 +- .../src/patch/redirect/upstream/mod.rs | 8 +- .../src/patch/redirect/upstream/pypi_locks.rs | 11 +- crates/socket-patch-core/src/policy/mod.rs | 7 +- crates/socket-patch-core/src/policy/report.rs | 4 +- .../src/policy/socket_yml.rs | 27 +- crates/socket-patch-core/src/policy/tests.rs | 29 +- crates/socket-patch-core/src/rollout/stage.rs | 11 +- crates/socket-patch-core/src/telemetry.rs | 4 +- .../socket-patch-core/src/update/download.rs | 13 +- .../socket-patch-core/src/update/release.rs | 39 +- .../src/utils/group_commit.rs | 21 +- .../src/utils/line_endings.rs | 1 - .../src/utils/lock_fragments.rs | 9 + crates/socket-patch-core/src/utils/process.rs | 15 +- .../src/utils/python_script.rs | 7 +- .../socket-patch-core/src/vendor/bun_lockb.rs | 9 +- .../src/vendor/cargo_lock.rs | 4 +- crates/socket-patch-core/src/vendor/gem.rs | 14 +- .../src/vendor/lock_inventory/view.rs | 4 +- .../src/vendor/lock_inventory/wired.rs | 2 +- .../socket-patch-core/src/vendor/prestage.rs | 5 +- .../src/vendor/toml_surgery.rs | 3 +- .../src/vex/discover/cargo.rs | 29 +- .../src/vex/discover/maven.rs | 8 +- .../src/vex/discover/nuget.rs | 2 +- .../tests/covgap_api_blob_fetcher.rs | 5 +- .../tests/covgap_crawlers_composer_crawler.rs | 6 +- .../tests/hosted_inventory.rs | 10 +- .../socket-patch-core/tests/poetry_hosted.rs | 81 ++- .../tests/telemetry_helpers_e2e.rs | 6 +- .../tests/upstream_restore_golden.rs | 423 ++++++++++++---- crates/socket-patch-core/tests/uv_hosted.rs | 4 +- crates/socket-patch-node/src/lib.rs | 6 +- 120 files changed, 2270 insertions(+), 794 deletions(-) diff --git a/crates/socket-patch-cli/src/commands/list.rs b/crates/socket-patch-cli/src/commands/list.rs index 8fc77fab1..44c719038 100644 --- a/crates/socket-patch-cli/src/commands/list.rs +++ b/crates/socket-patch-cli/src/commands/list.rs @@ -431,7 +431,10 @@ pub async fn run(args: ListArgs) -> i32 { detail: detail.clone(), }); } else if !args.common.silent { - eprintln!("Warning: {}", crate::commands::rollback::capitalize_first(detail)); + eprintln!( + "Warning: {}", + crate::commands::rollback::capitalize_first(detail) + ); } } let vendor_state = crate::commands::vendor_state_lenient(&loaded.vendor, args.common.silent); @@ -773,12 +776,18 @@ mod tests { let listings = HostedListing::from_pins( &[ pin("pkg:npm/minimist@1.2.2", &record.uuid), - pin("pkg:npm/other@1.0.0", "33333333-3333-4333-8333-333333333333"), + pin( + "pkg:npm/other@1.0.0", + "33333333-3333-4333-8333-333333333333", + ), ], Some(&legacy), ); assert_eq!(listings[0].record, record); - assert_eq!(listings[1].record.uuid, "33333333-3333-4333-8333-333333333333"); + assert_eq!( + listings[1].record.uuid, + "33333333-3333-4333-8333-333333333333" + ); assert!(listings[1].record.vulnerabilities.is_empty()); assert_eq!(listings[1].lockfiles, vec!["yarn.lock".to_string()]); } diff --git a/crates/socket-patch-cli/src/commands/mod.rs b/crates/socket-patch-cli/src/commands/mod.rs index ea45e6915..34ae4b1a3 100644 --- a/crates/socket-patch-cli/src/commands/mod.rs +++ b/crates/socket-patch-cli/src/commands/mod.rs @@ -1,7 +1,7 @@ pub mod apply; pub(crate) mod bun_preflight; -pub(crate) mod context; pub(crate) mod composer_hints; +pub(crate) mod context; pub(crate) mod fetch_stage; pub mod get; pub mod hosted_bundle; @@ -9,11 +9,11 @@ pub mod list; pub(crate) mod lock_cli; pub mod remove; pub mod repair; -pub(crate) mod vendored_backend; pub mod rollback; pub mod scan; pub mod update; pub mod vendor; +pub(crate) mod vendored_backend; pub mod vex; pub(crate) mod vex_consumed; pub(crate) mod vex_sources; @@ -141,9 +141,11 @@ pub(crate) async fn hosted_state_from_lockfiles( common: &crate::args::GlobalArgs, root: &Path, ) -> socket_patch_core::patch::redirect::RedirectState { - hosted_state_from_pins(&socket_patch_core::patch::redirect::upstream::HostedPin::all( - &discover_wiring(common, root).await, - )) + hosted_state_from_pins( + &socket_patch_core::patch::redirect::upstream::HostedPin::all( + &discover_wiring(common, root).await, + ), + ) } /// [`hosted_state_from_lockfiles`] over already-discovered pins. A purl @@ -153,10 +155,8 @@ pub(crate) fn hosted_state_from_pins( ) -> socket_patch_core::patch::redirect::RedirectState { let mut state = socket_patch_core::patch::redirect::RedirectState::new(); for pin in pins { - state - .records - .entry(pin.purl.clone()) - .or_insert_with(|| socket_patch_core::manifest::schema::PatchRecord { + state.records.entry(pin.purl.clone()).or_insert_with(|| { + socket_patch_core::manifest::schema::PatchRecord { uuid: pin.uuid.clone(), exported_at: String::new(), files: Default::default(), @@ -164,7 +164,8 @@ pub(crate) fn hosted_state_from_pins( description: String::new(), license: String::new(), tier: String::new(), - }); + } + }); } state } @@ -191,4 +192,3 @@ pub(crate) fn vendor_state_lenient( } } } - diff --git a/crates/socket-patch-cli/src/commands/scan/discovery.rs b/crates/socket-patch-cli/src/commands/scan/discovery.rs index f8e6b467c..79ca66737 100644 --- a/crates/socket-patch-cli/src/commands/scan/discovery.rs +++ b/crates/socket-patch-cli/src/commands/scan/discovery.rs @@ -168,29 +168,32 @@ pub(crate) async fn vendored_ledger_supplement( } // `(ledger key, base purl, entry)`; the artifact fallback has no // entries to probe, so it never reports unwired keys. - let candidates: Vec<(String, String, Option<&socket_patch_core::vendor::VendorEntry>)> = - match state { - Ok(state) => state - .entries - .iter() - .map(|(key, entry)| { - ( - key.clone(), - strip_purl_qualifiers(&entry.base_purl).to_string(), - Some(entry), - ) - }) - .collect(), - // Corrupt/unreadable ledger (a MISSING file is Ok(empty) above): - // recover the vendored set from the committed artifacts, or - // `scan --prune` (whose ledger exemption also degrades to empty) - // would delete still-vendored packages' manifest entries and blobs. - Err(_) => vendored_purls_from_artifacts(common) - .await - .into_iter() - .map(|base| (base.clone(), base, None)) - .collect(), - }; + let candidates: Vec<( + String, + String, + Option<&socket_patch_core::vendor::VendorEntry>, + )> = match state { + Ok(state) => state + .entries + .iter() + .map(|(key, entry)| { + ( + key.clone(), + strip_purl_qualifiers(&entry.base_purl).to_string(), + Some(entry), + ) + }) + .collect(), + // Corrupt/unreadable ledger (a MISSING file is Ok(empty) above): + // recover the vendored set from the committed artifacts, or + // `scan --prune` (whose ledger exemption also degrades to empty) + // would delete still-vendored packages' manifest entries and blobs. + Err(_) => vendored_purls_from_artifacts(common) + .await + .into_iter() + .map(|base| (base.clone(), base, None)) + .collect(), + }; // Composer by release identity: a ledger `@3.0.2.0` is the crawled // `@3.0.2`, not a second package to supplement. let key = |p: &str| composer_purl_identity(p).unwrap_or_else(|| normalize_purl(p).into_owned()); @@ -1045,7 +1048,9 @@ mod tests { ..GlobalArgs::default() }; let state = socket_patch_core::vendor::load_state(root).await; - vendored_ledger_supplement(&args, crawled, &state).await.packages + vendored_ledger_supplement(&args, crawled, &state) + .await + .packages } /// A ledger entry vendored as `@3.0.2.0` is the crawled composer @@ -1080,7 +1085,9 @@ mod tests { out.iter().map(|p| &p.purl).collect::>() ); - let out = vendored_ledger_supplement(&args, &[], &Ok(state)).await.packages; + let out = vendored_ledger_supplement(&args, &[], &Ok(state)) + .await + .packages; assert_eq!( out.iter().map(|p| p.purl.as_str()).collect::>(), vec!["pkg:composer/psr/log@3.0.2.0"] @@ -1183,7 +1190,10 @@ mod tests { let state = npm_ledger_with_lock(tmp.path(), lock.as_deref()).await; let out = vendored_ledger_supplement(&args, &[], &state).await; assert_eq!( - out.packages.iter().map(|p| p.purl.as_str()).collect::>(), + out.packages + .iter() + .map(|p| p.purl.as_str()) + .collect::>(), vec!["pkg:npm/left-pad@1.3.0"], "lock={lock:?}" ); diff --git a/crates/socket-patch-cli/src/commands/scan/hosted.rs b/crates/socket-patch-cli/src/commands/scan/hosted.rs index e6e48a140..6e2b0bea8 100644 --- a/crates/socket-patch-cli/src/commands/scan/hosted.rs +++ b/crates/socket-patch-cli/src/commands/scan/hosted.rs @@ -982,7 +982,8 @@ pub(crate) async fn run_redirect_selected( socket_patch_core::utils::fs::read_regular_to_string_sync(path).ok() }) }; - let rewrite_options = || RewriteOptions { + let rewrite_options = || { + RewriteOptions { dry_run: common.dry_run, targets_pipenv_lock, pipenv_major, @@ -994,6 +995,7 @@ pub(crate) async fn run_redirect_selected( npm_allow_remote_config: !common.no_npm_allow_remote_config, npm_outer: &npm_outer, blocking: true, + } }; // The rollout gate plans again without its deferred rows: keep what // the second pass needs. @@ -2431,13 +2433,19 @@ fn join_names(names: &[String], max: usize) -> String { /// artifacts, then verify with `vex`. After a vendored→hosted takeover /// (`vendored_removed`) the commit also has to carry the deleted vendored /// ledger entries and artifacts. -fn format_next_steps(files: &[String], edits: &[socket_patch_core::patch::redirect::FileEdit], vendored_removed: bool) -> Vec { +fn format_next_steps( + files: &[String], + edits: &[socket_patch_core::patch::redirect::FileEdit], + vendored_removed: bool, +) -> Vec { if files.is_empty() && !vendored_removed { return Vec::new(); } let mut commit: Vec = Vec::new(); if vendored_removed { - commit.push(".socket/vendor/ (the removed vendored ledger entries and artifacts)".to_string()); + commit.push( + ".socket/vendor/ (the removed vendored ledger entries and artifacts)".to_string(), + ); } commit.extend(files.iter().cloned()); let npm = files @@ -4624,19 +4632,43 @@ mod tests { use super::npm_allow_remote_one_line; let hosts = ["patch.socket.dev"]; let cases = [ - (npm_allow_remote_configured_detail(&hosts, true, false), "Note: set"), - (npm_allow_remote_configured_detail(&hosts, false, false), "Note: set"), - (npm_allow_remote_configured_detail(&hosts, true, true), "Note: would set"), - (npm_allow_remote_already_detail(&hosts), "Note: .npmrc already"), - (npm_allow_remote_user_set_detail(&hosts, "none"), "Warning: npm >=12"), - (npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), "Warning: npm >=12"), + ( + npm_allow_remote_configured_detail(&hosts, true, false), + "Note: set", + ), + ( + npm_allow_remote_configured_detail(&hosts, false, false), + "Note: set", + ), + ( + npm_allow_remote_configured_detail(&hosts, true, true), + "Note: would set", + ), + ( + npm_allow_remote_already_detail(&hosts), + "Note: .npmrc already", + ), + ( + npm_allow_remote_user_set_detail(&hosts, "none"), + "Warning: npm >=12", + ), + ( + npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), + "Warning: npm >=12", + ), (npm_allow_remote_manual_detail(&hosts), "Warning: npm >=12"), - (npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), "Warning: npm >=12"), + ( + npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), + "Warning: npm >=12", + ), ]; for (detail, start) in cases { let line = npm_allow_remote_one_line(&detail); assert!(line.starts_with(start), "{line}"); - assert!(!line.contains('\n') && line.ends_with("(details: --verbose)."), "{line}"); + assert!( + !line.contains('\n') && line.ends_with("(details: --verbose)."), + "{line}" + ); } } } diff --git a/crates/socket-patch-cli/src/commands/scan/policy.rs b/crates/socket-patch-cli/src/commands/scan/policy.rs index 98b1ecc45..9e53b7a55 100644 --- a/crates/socket-patch-cli/src/commands/scan/policy.rs +++ b/crates/socket-patch-cli/src/commands/scan/policy.rs @@ -11,9 +11,9 @@ use socket_patch_core::api::ranking::cmp_search_results; use socket_patch_core::api::types::PatchSearchResult; use socket_patch_core::manifest::schema::PatchManifest; use socket_patch_core::policy::{ - canon, find_repo_root_with_warnings, policy_block, FilteredEntry, RetainedEntry, patch_severity_order, repo_relative_checked, sanitize, severity_name, - DiskPolicyFs, FilterReason, Offers, PolicyError, PolicySource, PolicyWarning, Root, SelectionPolicy, - PATCHES_DISABLED, + canon, find_repo_root_with_warnings, patch_severity_order, policy_block, repo_relative_checked, + sanitize, severity_name, DiskPolicyFs, FilterReason, FilteredEntry, Offers, PolicyError, + PolicySource, PolicyWarning, RetainedEntry, Root, SelectionPolicy, PATCHES_DISABLED, }; use socket_patch_core::utils::purl::normalize_purl; @@ -42,12 +42,18 @@ pub(crate) struct InvocationPolicy { /// Load the policy for `args` (4.5): `--global` scans have no repo and read /// no file; everything else reads the repo root's socket.yml. pub(crate) fn load_invocation_policy(args: &ScanArgs) -> Result { - let overrides = args.socket_yml.overrides().map_err(PolicyLoadError::Usage)?; + let overrides = args + .socket_yml + .overrides() + .map_err(PolicyLoadError::Usage)?; let cwd = std::fs::canonicalize(&args.common.cwd).unwrap_or_else(|_| args.common.cwd.clone()); if args.common.is_global() { - let policy = SelectionPolicy::load(&socket_patch_core::policy::MemoryPolicyFs::default(), &overrides) - .map_err(PolicyLoadError::Policy)? - .0; + let policy = SelectionPolicy::load( + &socket_patch_core::policy::MemoryPolicyFs::default(), + &overrides, + ) + .map_err(PolicyLoadError::Policy)? + .0; return Ok(InvocationPolicy { policy, repo_root: cwd, @@ -56,8 +62,8 @@ pub(crate) fn load_invocation_policy(args: &ScanArgs) -> Result Self { + pub(crate) fn for_root( + invocation: &InvocationPolicy, + root_dir: &Path, + explicit: bool, + global: bool, + ) -> Self { let root_dir = std::fs::canonicalize(root_dir).unwrap_or_else(|_| root_dir.to_path_buf()); let project = repo_relative_checked(&invocation.repo_root, &root_dir).unwrap_or_default(); let root_verdict = if global { @@ -171,7 +182,9 @@ impl ScanPolicy { severity: None, }); } - let announce_warnings = !invocation.warned.swap(true, std::sync::atomic::Ordering::Relaxed); + let announce_warnings = !invocation + .warned + .swap(true, std::sync::atomic::Ordering::Relaxed); Self { policy: invocation.policy.clone(), warnings, @@ -224,7 +237,10 @@ impl ScanPolicy { /// exclude stays in the query (so `upgradeAvailable` can be reported) /// but joins the retained set, which never reaches a writer. pub(crate) fn admit_crawled(&self, purl: &str) -> bool { - let verdict = self.root_verdict.clone().and_then(|()| self.policy.admits_purl(purl)); + let verdict = self + .root_verdict + .clone() + .and_then(|()| self.policy.admits_purl(purl)); let reason = match verdict { Ok(()) => return true, Err(reason) => reason, @@ -334,7 +350,8 @@ impl ScanPolicy { // (not when a lower-ranked admitted patch simply wins). let top_withheld = self.policy.admits_severity(patch_severity_order(&group[0])); if let Err(reason) = top_withheld { - let upgrade_withheld = chosen.is_some() && chosen == recorded_at && recorded_at != Some(0); + let upgrade_withheld = + chosen.is_some() && chosen == recorded_at && recorded_at != Some(0); if chosen.is_none() || upgrade_withheld { report.filtered.push(FilteredEntry { purl: Some(canon(&purl)), @@ -522,17 +539,20 @@ pub(crate) fn policy_bypass_warnings( let verdict = if !policy.enabled() { Err(FilterReason::Disabled) } else { - root_verdict.clone().and_then(|()| policy.admits_purl(purl)).and_then(|()| { - // The floor only hides a package when none of its patches pass. - match group - .iter() - .map(|p| policy.admits_severity(patch_severity_order(p))) - .find(Result::is_ok) - { - Some(ok) => ok, - None => policy.admits_severity(patch_severity_order(group[0])), - } - }) + root_verdict + .clone() + .and_then(|()| policy.admits_purl(purl)) + .and_then(|()| { + // The floor only hides a package when none of its patches pass. + match group + .iter() + .map(|p| policy.admits_severity(patch_severity_order(p))) + .find(Result::is_ok) + { + Some(ok) => ok, + None => policy.admits_severity(patch_severity_order(group[0])), + } + }) }; if let Err(reason) = verdict { out.push(( diff --git a/crates/socket-patch-cli/src/commands/scan/rollout.rs b/crates/socket-patch-cli/src/commands/scan/rollout.rs index 82ef99e17..fe7470a83 100644 --- a/crates/socket-patch-cli/src/commands/scan/rollout.rs +++ b/crates/socket-patch-cli/src/commands/scan/rollout.rs @@ -4,8 +4,10 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; -use socket_patch_core::rollout::{canonical_base_purl, severity_label, MaxNew, MaxNewSource, Recorded, RolloutPlan}; pub(crate) use socket_patch_core::rollout::stage::*; +use socket_patch_core::rollout::{ + canonical_base_purl, severity_label, MaxNew, MaxNewSource, Recorded, RolloutPlan, +}; use super::discovery::UpdateInfo; @@ -208,11 +210,11 @@ pub(crate) fn human_lines( mod tests { use super::*; use socket_patch_core::api::types::PatchSearchResult; - use socket_patch_core::manifest::schema::PatchManifest; - use std::path::Path; use socket_patch_core::api::types::VulnerabilityResponse; + use socket_patch_core::manifest::schema::PatchManifest; use socket_patch_core::manifest::schema::PatchRecord; use std::collections::HashMap; + use std::path::Path; fn offer(purl: &str, uuid: &str, published: &str, severities: &[&str]) -> PatchSearchResult { PatchSearchResult { @@ -357,13 +359,21 @@ mod tests { let stored = manifest(&[("pkg:composer/psr/log@3.0.2.0", "old")]); let recorded = RecordedIndex::new(Some(&stored), &[]); let offers = offers_from_results( - &[offer("pkg:composer/psr/log@v3.0.2", "new", "2026-02-01T00:00:00Z", &["high"])], + &[offer( + "pkg:composer/psr/log@v3.0.2", + "new", + "2026-02-01T00:00:00Z", + &["high"], + )], false, ); let rows = classify(&offers, &recorded, ""); let plan = socket_patch_core::rollout::plan_rollout( rows.into_iter().map(|row| row.candidate).collect(), - &MaxNew { value: Some(0), source: MaxNewSource::Flag }, + &MaxNew { + value: Some(0), + source: MaxNewSource::Flag, + }, false, &BTreeSet::new(), ); diff --git a/crates/socket-patch-cli/src/commands/scan/rollout_args.rs b/crates/socket-patch-cli/src/commands/scan/rollout_args.rs index e4d251e98..f83636045 100644 --- a/crates/socket-patch-cli/src/commands/scan/rollout_args.rs +++ b/crates/socket-patch-cli/src/commands/scan/rollout_args.rs @@ -1,7 +1,6 @@ //! `scan --max-new-patches` (see the rollout guide, //! `docs/configuration.md#gradual-rollout`). - use clap::Args; pub(crate) use socket_patch_core::rollout::stage::RolloutCarry; use socket_patch_core::rollout::{resolve_max_new, MaxNew}; @@ -77,7 +76,6 @@ impl RolloutArgs { } } - #[cfg(test)] mod tests { use super::*; diff --git a/crates/socket-patch-cli/src/commands/vendor.rs b/crates/socket-patch-cli/src/commands/vendor.rs index 0289bf946..e9eedafe3 100644 --- a/crates/socket-patch-cli/src/commands/vendor.rs +++ b/crates/socket-patch-cli/src/commands/vendor.rs @@ -283,10 +283,7 @@ pub(crate) async fn dispatch_revert_one_opts( /// entry (fail-safe): ecosystems other than npm, cargo and pypi (whose /// probe covers the requirements flavor only) have no in-use probe yet, /// and a missing/unreadable lockfile proves nothing. -pub(crate) async fn dispatch_in_use_one( - entry: &VendorEntry, - project_root: &Path, -) -> Option { +pub(crate) async fn dispatch_in_use_one(entry: &VendorEntry, project_root: &Path) -> Option { match entry.ecosystem.as_str() { "npm" => vendor::npm_flavor::vendored_entry_in_use(entry, project_root).await, // Cargo probes the lock entry's shape: detached + `[patch]` pointing diff --git a/crates/socket-patch-cli/tests/apply/apply_network.rs b/crates/socket-patch-cli/tests/apply/apply_network.rs index 837057e18..7284a5e2f 100644 --- a/crates/socket-patch-cli/tests/apply/apply_network.rs +++ b/crates/socket-patch-cli/tests/apply/apply_network.rs @@ -940,7 +940,10 @@ async fn apply_online_ignores_legacy_package_archive_when_downloads_fail() { "a legacy package archive must not cover the patch; stdout={stdout}\nstderr={stderr}" ); let content = std::fs::read(tmp.path().join("node_modules/pkgcache/index.js")).unwrap(); - assert_eq!(content, before, "the file must not be patched from the legacy archive"); + assert_eq!( + content, before, + "the file must not be patched from the legacy archive" + ); let requests = mock.received_requests().await.unwrap_or_default(); let blob_path = format!("/v0/orgs/{ORG_SLUG}/patches/blob/{after_hash}"); @@ -1043,10 +1046,7 @@ async fn mismatch_blob_topup_probes_every_copy_of_a_duplicated_package() { v["summary"]["applied"], 1, "the drifted nested copy must be warn-overwritten.\nstdout={v:#}" ); - assert_eq!( - v["summary"]["failed"], 0, - "no copy may fail.\nstdout={v:#}" - ); + assert_eq!(v["summary"]["failed"], 0, "no copy may fail.\nstdout={v:#}"); // The nested copy's blob was fetched on demand… let requests = mock.received_requests().await.unwrap(); diff --git a/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs b/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs index 6e849f90c..5bc4eacd7 100644 --- a/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs +++ b/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs @@ -201,7 +201,9 @@ fn apply_stderr_warning_gates_on_silent() { "non-silent stderr must carry the {CODE} warning; got:\n{stderr}" ); assert_eq!( - stderr.matches("Warning: bundler app config BUNDLE_PATH").count(), + stderr + .matches("Warning: bundler app config BUNDLE_PATH") + .count(), 1, "exactly ONE warning line (not one per discovery call); got:\n{stderr}" ); diff --git a/crates/socket-patch-cli/tests/cli/covgap_output.rs b/crates/socket-patch-cli/tests/cli/covgap_output.rs index 65cf0b67f..1f5e1c860 100644 --- a/crates/socket-patch-cli/tests/cli/covgap_output.rs +++ b/crates/socket-patch-cli/tests/cli/covgap_output.rs @@ -168,9 +168,8 @@ fn run_in_pty_inner( .expect("spawn socket-patch in PTY"); drop(pair.slave); - let reader_handle = crate::pty_io::PtyOutput::spawn( - pair.master.try_clone_reader().expect("clone reader"), - ); + let reader_handle = + crate::pty_io::PtyOutput::spawn(pair.master.try_clone_reader().expect("clone reader")); // Watchdog: detached kill after `timeout`; a no-op if the child exits // naturally first. @@ -261,7 +260,10 @@ fn remove_interactive_bare_enter_proceeds_with_default_yes() { "\n", Duration::from_secs(15), ); - assert_eq!(code, 0, "remove with bare Enter must succeed; got: {output}"); + assert_eq!( + code, 0, + "remove with bare Enter must succeed; got: {output}" + ); // The interactive confirm MUST have run — otherwise this test passes // vacuously against a regression that drops the TTY gate and // auto-proceeds. Match the distinctive prompt verbatim (the loose diff --git a/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs b/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs index 6f744bfe4..a7387e225 100644 --- a/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs +++ b/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs @@ -112,9 +112,8 @@ fn run_in_pty_bytes(args: &[&str], cwd: &Path, input: &[u8], timeout: Duration) // closed. The previous design used a chunked read+mpsc loop // because it interleaved with a try_wait poll; the simplified // design serializes wait → drop master → read_to_end joins. - let reader_handle = crate::pty_io::PtyOutput::spawn( - pair.master.try_clone_reader().expect("clone reader"), - ); + let reader_handle = + crate::pty_io::PtyOutput::spawn(pair.master.try_clone_reader().expect("clone reader")); // Watchdog: detach a thread that kills the child after `timeout`. // The cloned ChildKiller is independent of the main `child` diff --git a/crates/socket-patch-cli/tests/cli_config_fallback.rs b/crates/socket-patch-cli/tests/cli_config_fallback.rs index df19585db..530a53c5f 100644 --- a/crates/socket-patch-cli/tests/cli_config_fallback.rs +++ b/crates/socket-patch-cli/tests/cli_config_fallback.rs @@ -59,8 +59,7 @@ fn scan_cmd(project: &Path, data_dir: &Path) -> Command { let mut cmd = Command::new(BINARY); // Human mode: core's proxy advisory (the oracle below) is muted under // `--json`/`--silent`. - cmd.args(["scan", "-e", "npm", "--cwd"]) - .arg(project); + cmd.args(["scan", "-e", "npm", "--cwd"]).arg(project); for (key, _) in std::env::vars_os() { let name = key.to_string_lossy(); if name.starts_with("SOCKET_") { @@ -298,7 +297,9 @@ async fn corrupt_config_warns_and_keeps_json_stdout_clean() { json_cmd.arg("--json"); let json_out = run(json_cmd); assert!( - json_out.stderr.contains("could not parse socket-cli config"), + json_out + .stderr + .contains("could not parse socket-cli config"), "the parse warning must reach stderr under --json too; got:\n{}", json_out.stderr ); diff --git a/crates/socket-patch-cli/tests/cli_get_silent.rs b/crates/socket-patch-cli/tests/cli_get_silent.rs index 4e43c353d..72f454a6a 100644 --- a/crates/socket-patch-cli/tests/cli_get_silent.rs +++ b/crates/socket-patch-cli/tests/cli_get_silent.rs @@ -25,10 +25,7 @@ fn run_get(cwd: &Path, args: &[&str]) -> (i32, String) { for var in GLOBAL_ARG_ENV_VARS { cmd.env_remove(var); } - for var in [ - "SOCKET_SAVE_ONLY", - "SOCKET_ALL_RELEASES", - ] { + for var in ["SOCKET_SAVE_ONLY", "SOCKET_ALL_RELEASES"] { cmd.env_remove(var); } cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); diff --git a/crates/socket-patch-cli/tests/cli_parse_list.rs b/crates/socket-patch-cli/tests/cli_parse_list.rs index 8c997686f..9a850490d 100644 --- a/crates/socket-patch-cli/tests/cli_parse_list.rs +++ b/crates/socket-patch-cli/tests/cli_parse_list.rs @@ -370,7 +370,11 @@ fn missing_manifest_under_valid_cwd_is_not_an_error_via_binary() { let out = run_list_binary(tmp.path(), &["--json"]); let v: serde_json::Value = serde_json::from_str(String::from_utf8_lossy(&out.stdout).trim()) .expect("stdout must be valid JSON envelope"); - assert_eq!(out.status.code(), Some(0), "missing manifest is an empty list"); + assert_eq!( + out.status.code(), + Some(0), + "missing manifest is an empty list" + ); assert_eq!(v["status"], "success", "envelope: {v}"); assert_eq!(v["summary"]["discovered"], 0, "envelope: {v}"); } @@ -1313,7 +1317,10 @@ fn missing_manifest_with_corrupt_ledger_keeps_warning_in_the_envelope_via_binary assert_eq!(v["status"], "success", "envelope={v}"); let warnings = v["warnings"].as_array().expect("warnings[] present"); assert_eq!(warnings.len(), 1, "envelope={v}"); - assert_eq!(warnings[0]["code"], "redirect_ledger_corrupt", "envelope={v}"); + assert_eq!( + warnings[0]["code"], "redirect_ledger_corrupt", + "envelope={v}" + ); assert!( out.stderr.is_empty(), "--json must keep stderr clean: {}", diff --git a/crates/socket-patch-cli/tests/cli_parse_rollback.rs b/crates/socket-patch-cli/tests/cli_parse_rollback.rs index c8b77af5e..f1590292e 100644 --- a/crates/socket-patch-cli/tests/cli_parse_rollback.rs +++ b/crates/socket-patch-cli/tests/cli_parse_rollback.rs @@ -366,7 +366,11 @@ fn bare_bool_does_not_consume_next_token() { /// relied on the rejection get a test-visible flip instead of a silent one. #[test] fn multiple_targets_parse_in_order() { - let args = parse_rollback(&["pkg:npm/foo@1", "packages/api/**", "b0630680-4da6-45f9-bba8-b888e0ffd58c"]); + let args = parse_rollback(&[ + "pkg:npm/foo@1", + "packages/api/**", + "b0630680-4da6-45f9-bba8-b888e0ffd58c", + ]); assert_eq!( args.targets, vec![ diff --git a/crates/socket-patch-cli/tests/cli_parse_scan.rs b/crates/socket-patch-cli/tests/cli_parse_scan.rs index ab81fa6eb..eff55ee79 100644 --- a/crates/socket-patch-cli/tests/cli_parse_scan.rs +++ b/crates/socket-patch-cli/tests/cli_parse_scan.rs @@ -898,7 +898,11 @@ fn max_new_patches_takes_a_count_or_none() { ("NONE", None), ] { let args = parse_scan(&["--max-new-patches", raw]); - assert_eq!(args.rollout.max_new_patches, Some(MaxNewPatches(want)), "{raw}"); + assert_eq!( + args.rollout.max_new_patches, + Some(MaxNewPatches(want)), + "{raw}" + ); } } @@ -989,20 +993,33 @@ fn min_severity_flag_and_env() { assert_eq!(parse_scan(&[]).socket_yml.min_severity, None); assert_eq!(overrides(&[], &[]).unwrap().min_severity, None); assert_eq!( - overrides(&["--min-severity", "High"], &[]).unwrap().min_severity, + overrides(&["--min-severity", "High"], &[]) + .unwrap() + .min_severity, Some((Some(1), OverrideSource::Flag)) ); assert_eq!( - overrides(&["--min-severity", "none"], &[("SOCKET_MIN_SEVERITY", "critical")]).unwrap().min_severity, + overrides( + &["--min-severity", "none"], + &[("SOCKET_MIN_SEVERITY", "critical")] + ) + .unwrap() + .min_severity, Some((None, OverrideSource::Flag)) ); assert_eq!( - overrides(&[], &[("SOCKET_MIN_SEVERITY", "moderate")]).unwrap().min_severity, + overrides(&[], &[("SOCKET_MIN_SEVERITY", "moderate")]) + .unwrap() + .min_severity, Some((Some(2), OverrideSource::Env)) ); - assert_eq!(overrides(&[], &[("SOCKET_MIN_SEVERITY", "")]).unwrap().min_severity, None); + assert_eq!( + overrides(&[], &[("SOCKET_MIN_SEVERITY", "")]) + .unwrap() + .min_severity, + None + ); assert!(overrides(&[], &[("SOCKET_MIN_SEVERITY", "severe")]).is_err()); assert!(try_parse_scan(&["--min-severity", "severe"]).is_err()); assert!(overrides(&["--no-socket-yml"], &[]).unwrap().bypass); } - diff --git a/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs b/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs index 444dd2a3b..049d8356b 100644 --- a/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs +++ b/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs @@ -149,7 +149,9 @@ fn apply_silent_online_download_failure_keeps_error_output() { ); let chatter = stderr_chatter(&stderr); assert!( - chatter.iter().any(|l| l.contains("could not be downloaded")), + chatter + .iter() + .any(|l| l.contains("could not be downloaded")), "--silent must keep the download-failure error (errors only, \ never nothing); stderr was: {stderr:?}" ); diff --git a/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs b/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs index 54ddf7441..235030104 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs +++ b/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs @@ -566,8 +566,7 @@ async fn wet_takeover_refuses_unrevertable_vendored_flavor_fail_closed() { "the human skipped line must name purl + reason; stderr=\n{stderr}" ); assert!( - stderr.contains("Warning: ") - && stderr.contains("could not be reverted"), + stderr.contains("Warning: ") && stderr.contains("could not be reverted"), "the takeover pre-warning must reach human stderr; stderr=\n{stderr}" ); } @@ -814,7 +813,10 @@ async fn zero_grant_wet_run_ignores_a_malformed_pre_v5_ledger() { let lock_before = std::fs::read(root.join("package-lock.json")).unwrap(); let assert_ignored = |code: i32, doc: &Value, label: &str| { - assert_eq!(code, 0, "{label}: a pre-v5 ledger is never an error: {doc:#}"); + assert_eq!( + code, 0, + "{label}: a pre-v5 ledger is never an error: {doc:#}" + ); assert_eq!(doc["status"], "success", "{label}: {doc:#}"); assert!( !doc.to_string().contains("redirect-state.json"), @@ -1017,7 +1019,10 @@ async fn hosted_human_empty_discovery_ignores_a_malformed_pre_v5_ledger() { for extra in [&[][..], &["--silent"][..]] { let (code, stdout, stderr) = scan_hosted(root, &server.uri(), extra, &[]); - assert_eq!(code, 0, "{extra:?}: an empty discovery exits 0; stderr=\n{stderr}"); + assert_eq!( + code, 0, + "{extra:?}: an empty discovery exits 0; stderr=\n{stderr}" + ); if extra.is_empty() { assert!( stdout.contains("No patches available for installed packages."), @@ -1404,16 +1409,22 @@ async fn native_bun_lockb_hosting_dry_run_rerun_and_rollback_without_bun() { ], &env, ); - assert_eq!(code, 1, "a binary bun.lockb pin is refused: {stdout}\n{stderr}"); + assert_eq!( + code, 1, + "a binary bun.lockb pin is refused: {stdout}\n{stderr}" + ); let doc: Value = serde_json::from_str(&stdout).unwrap_or_else(|e| panic!("{e}: {stdout}")); assert_eq!(doc["status"], "partial_failure", "{doc:#}"); - let failed = doc["hosted"]["failed"].as_array().unwrap_or_else(|| panic!("{doc:#}")); + let failed = doc["hosted"]["failed"] + .as_array() + .unwrap_or_else(|| panic!("{doc:#}")); assert_eq!(failed.len(), 1, "{doc:#}"); assert_eq!(failed[0]["purl"], purl, "{doc:#}"); let error = failed[0]["error"].as_str().unwrap_or_default(); assert!( - error.starts_with(&format!("cannot restore {purl} to its upstream registry entry: ")) - && error.contains("bun.lockb") + error.starts_with(&format!( + "cannot restore {purl} to its upstream registry entry: " + )) && error.contains("bun.lockb") && error.contains("git checkout"), "{error}" ); @@ -1819,7 +1830,9 @@ async fn unreadable_pnpm_workspace_gets_warning_only_guidance_in_a_live_run() { "the unreadable workspace file must be left byte-identical" ); assert!( - !tmp.path().join(".socket/vendor/redirect-state.json").exists(), + !tmp.path() + .join(".socket/vendor/redirect-state.json") + .exists(), "v5 hosted mode writes no redirect ledger" ); } @@ -1931,9 +1944,8 @@ async fn live_hosted_overlap_fires_redirect_supersedes_vendored() { let (code, _stdout, stderr) = scan_hosted(root, &server.uri(), &psu, &[]); assert_eq!(code, 0, "human overlap run exits 0; stderr=\n{stderr}"); assert!( - stderr.contains( - "Warning: Hosted wiring superseded the vendored ledger for:" - ) && stderr.contains(XPURL), + stderr.contains("Warning: Hosted wiring superseded the vendored ledger for:") + && stderr.contains(XPURL), "the supersedes warning must reach human stderr; stderr=\n{stderr}" ); } @@ -1981,8 +1993,7 @@ async fn human_dry_run_prints_would_rewrite_pnpm_guidance_and_vex_skip() { "the requested-but-skipped VEX must be announced; stderr=\n{stderr}" ); assert!( - stderr.contains("Warning: ") - && stderr.contains("trustLockfile"), + stderr.contains("Warning: ") && stderr.contains("trustLockfile"), "the pnpm trust guidance must reach human stderr; stderr=\n{stderr}" ); assert!( @@ -2429,7 +2440,8 @@ async fn human_pnpm_rerun_prints_only_the_reminder_and_heal_restores_guidance() let (code, stdout, stderr) = scan_hosted(root, &server.uri(), &[], &[]); assert_eq!(code, 0, "stdout=\n{stdout}\nstderr=\n{stderr}"); assert!( - engine_stdout(&stdout).starts_with("Switched 1 package to hosted patches; rewrote 2 files.\n"), + engine_stdout(&stdout) + .starts_with("Switched 1 package to hosted patches; rewrote 2 files.\n"), "{stdout}" ); // Everything from the pnpm warning on (the lines above it are the diff --git a/crates/socket-patch-cli/tests/e2e_bun_lockb.rs b/crates/socket-patch-cli/tests/e2e_bun_lockb.rs index 413bb91a6..60f369c1e 100644 --- a/crates/socket-patch-cli/tests/e2e_bun_lockb.rs +++ b/crates/socket-patch-cli/tests/e2e_bun_lockb.rs @@ -1156,7 +1156,10 @@ async fn workspace_text_migration_heals_on_rerun() { } let output = command(&fixture.reader, &checkout) .args(["install", "--frozen-lockfile", "--ignore-scripts"]) - .env("BUN_INSTALL_CACHE_DIR", fixture.temp.path().join("text-cache")) + .env( + "BUN_INSTALL_CACHE_DIR", + fixture.temp.path().join("text-cache"), + ) .env("BUN_INSTALL", fixture.temp.path().join("text-home")) .output() .unwrap(); diff --git a/crates/socket-patch-cli/tests/e2e_cargo.rs b/crates/socket-patch-cli/tests/e2e_cargo.rs index 3978aff96..73c6acaef 100644 --- a/crates/socket-patch-cli/tests/e2e_cargo.rs +++ b/crates/socket-patch-cli/tests/e2e_cargo.rs @@ -204,8 +204,7 @@ async fn scan_discovers_fake_registry_crates() { "Expected human scan to report exactly 'Found 2 packages (2 cargo)', got:\n{combined}" ); assert!( - !combined.contains("No packages found") - && !combined.contains("No packages found"), + !combined.contains("No packages found") && !combined.contains("No packages found"), "scan reported no packages despite a populated registry:\n{combined}" ); @@ -262,8 +261,7 @@ async fn scan_discovers_vendor_crates() { "Expected human scan to report exactly 'Found 1 package (1 cargo)', got:\n{combined}" ); assert!( - !combined.contains("No packages found") - && !combined.contains("No packages found"), + !combined.contains("No packages found") && !combined.contains("No packages found"), "scan reported no packages despite a populated vendor dir:\n{combined}" ); diff --git a/crates/socket-patch-cli/tests/e2e_gem.rs b/crates/socket-patch-cli/tests/e2e_gem.rs index db8af0af8..f6f189113 100644 --- a/crates/socket-patch-cli/tests/e2e_gem.rs +++ b/crates/socket-patch-cli/tests/e2e_gem.rs @@ -583,7 +583,11 @@ fn test_gem_dry_run() { let gem_dir = find_gem_dir(cwd); // Download without applying. - assert_run_ok(cwd, &["get", GEM_UUID, "--mode", "agent", "--no-apply"], "get --no-apply"); + assert_run_ok( + cwd, + &["get", GEM_UUID, "--mode", "agent", "--no-apply"], + "get --no-apply", + ); // Read manifest to get file list and expected hashes. let manifest_path = cwd.join(".socket/manifest.json"); diff --git a/crates/socket-patch-cli/tests/e2e_maven.rs b/crates/socket-patch-cli/tests/e2e_maven.rs index 22d7e940d..00937ae6c 100644 --- a/crates/socket-patch-cli/tests/e2e_maven.rs +++ b/crates/socket-patch-cli/tests/e2e_maven.rs @@ -177,8 +177,7 @@ async fn scan_discovers_maven_artifacts() { // Must NOT have hit the empty-crawl path — that line *also* contains // the word "packages". assert!( - !combined.contains("No packages found") - && !combined.contains("No packages found"), + !combined.contains("No packages found") && !combined.contains("No packages found"), "scan reported zero packages — Maven discovery did not run:\n{combined}" ); assert!( diff --git a/crates/socket-patch-cli/tests/e2e_npm.rs b/crates/socket-patch-cli/tests/e2e_npm.rs index 89f40f9a5..7486a85c9 100644 --- a/crates/socket-patch-cli/tests/e2e_npm.rs +++ b/crates/socket-patch-cli/tests/e2e_npm.rs @@ -286,7 +286,11 @@ fn test_npm_dry_run() { assert_eq!(git_sha256_file(&index_js), BEFORE_HASH); // Download the patch *without* applying. - assert_run_ok(cwd, &["get", NPM_UUID, "--mode", "agent", "--no-apply"], "get --no-apply"); + assert_run_ok( + cwd, + &["get", NPM_UUID, "--mode", "agent", "--no-apply"], + "get --no-apply", + ); // File should still be original. assert_eq!( diff --git a/crates/socket-patch-cli/tests/e2e_nuget.rs b/crates/socket-patch-cli/tests/e2e_nuget.rs index ce4cc4998..f8ec8eb1e 100644 --- a/crates/socket-patch-cli/tests/e2e_nuget.rs +++ b/crates/socket-patch-cli/tests/e2e_nuget.rs @@ -227,7 +227,8 @@ async fn scan_discovers_global_cache_packages() { // "packages" substring check would also match). assert!( !combined.contains("No packages found") - && !combined.contains("No packages found") && !combined.contains("No global packages found"), + && !combined.contains("No packages found") + && !combined.contains("No global packages found"), "scan failed to discover the fake global cache:\n{combined}" ); // Exactly the two packages we planted (Newtonsoft.Json, System.Text.Json), @@ -285,7 +286,8 @@ async fn scan_discovers_legacy_packages() { ); assert!( !combined.contains("No packages found") - && !combined.contains("No packages found") && !combined.contains("No global packages found"), + && !combined.contains("No packages found") + && !combined.contains("No global packages found"), "scan failed to discover the legacy packages/ layout:\n{combined}" ); // Exactly the single legacy package we planted (Newtonsoft.Json.13.0.3), diff --git a/crates/socket-patch-cli/tests/e2e_pypi.rs b/crates/socket-patch-cli/tests/e2e_pypi.rs index 4531d1173..d84c6db20 100644 --- a/crates/socket-patch-cli/tests/e2e_pypi.rs +++ b/crates/socket-patch-cli/tests/e2e_pypi.rs @@ -426,7 +426,11 @@ fn test_pypi_dry_run() { let original_hash = git_sha256_file(&messages_py); // Download without applying. - assert_run_ok(cwd, &["get", PYPI_UUID, "--mode", "agent", "--no-apply"], "get --no-apply"); + assert_run_ok( + cwd, + &["get", PYPI_UUID, "--mode", "agent", "--no-apply"], + "get --no-apply", + ); // File should be unchanged. assert_eq!( diff --git a/crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs b/crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs index bd737a2ca..9da70ac65 100644 --- a/crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs +++ b/crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs @@ -1856,7 +1856,10 @@ async fn gem_hosted_custom_git_source_is_refused_and_still_installs() { Driver::ScanVexCustomGitSource, ) .await; - assert!(fx.is_none(), "the custom git_source driver asserts in place"); + assert!( + fx.is_none(), + "the custom git_source driver asserts in place" + ); } /// #340: a `gem` declaration that continues on the next line must not be diff --git a/crates/socket-patch-cli/tests/e2e_redirect_yarn_berry_build.rs b/crates/socket-patch-cli/tests/e2e_redirect_yarn_berry_build.rs index c1ae3226c..4c96ef1b3 100644 --- a/crates/socket-patch-cli/tests/e2e_redirect_yarn_berry_build.rs +++ b/crates/socket-patch-cli/tests/e2e_redirect_yarn_berry_build.rs @@ -592,9 +592,9 @@ async fn berry_hosted_project_with( let root_pkg = std::fs::read_to_string(proj.join("package.json")).unwrap(); let root_pkg: serde_json::Value = serde_json::from_str(&root_pkg).unwrap(); assert!( - root_pkg["resolutions"] - .as_object() - .is_some_and(|r| r.iter().any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) + root_pkg["resolutions"].as_object().is_some_and(|r| r + .iter() + .any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) && v.as_str() == Some(hosted_url.as_str()))), "package.json must route {DEP} to the hosted tarball: {root_pkg}" ); diff --git a/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs b/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs index 62e9ef05d..29e90c39d 100644 --- a/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs +++ b/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs @@ -419,7 +419,11 @@ fn manifestless_agent_patch_is_not_attested(consumer: &Path, cargo_home: &Path) "description": "d" } }); - std::fs::write(&manifest_path, serde_json::to_vec_pretty(&manifest).unwrap()).unwrap(); + std::fs::write( + &manifest_path, + serde_json::to_vec_pretty(&manifest).unwrap(), + ) + .unwrap(); let out = run_vex(&bin, consumer, &run); assert_eq!(out.code, Some(0), "manifest-backed vex:\n{out}"); assert!( diff --git a/crates/socket-patch-cli/tests/e2e_safety_pnpm.rs b/crates/socket-patch-cli/tests/e2e_safety_pnpm.rs index 7af958619..783e7337a 100644 --- a/crates/socket-patch-cli/tests/e2e_safety_pnpm.rs +++ b/crates/socket-patch-cli/tests/e2e_safety_pnpm.rs @@ -293,7 +293,11 @@ fn apply_in_a_does_not_mutate_b_or_store() { }; // -- get + apply in proj_a only ---------------------------------- - assert_run_ok(&fx.proj_a, &["get", NPM_UUID, "--mode", "agent"], "socket-patch get"); + assert_run_ok( + &fx.proj_a, + &["get", NPM_UUID, "--mode", "agent"], + "socket-patch get", + ); // proj_a is patched. assert_eq!( @@ -397,7 +401,11 @@ fn pnpm_install_in_b_does_not_revert_a() { store_id }; - assert_run_ok(&fx.proj_a, &["get", NPM_UUID, "--mode", "agent"], "socket-patch get"); + assert_run_ok( + &fx.proj_a, + &["get", NPM_UUID, "--mode", "agent"], + "socket-patch get", + ); assert_eq!(git_sha256_file(&index_a), AFTER_HASH); // Re-run pnpm install in proj_b with frozen lockfile — this @@ -475,7 +483,11 @@ fn apply_in_pnpm_project_emits_layout_note() { let root = tempfile::tempdir().unwrap(); let fx = setup_two_pnpm_projects(root.path()); - let (_stdout, stderr) = assert_run_ok(&fx.proj_a, &["get", NPM_UUID, "--mode", "agent"], "socket-patch get"); + let (_stdout, stderr) = assert_run_ok( + &fx.proj_a, + &["get", NPM_UUID, "--mode", "agent"], + "socket-patch get", + ); // The exact phrasing is a stable contract. A bare `contains("pnpm")` // is worthless here — every pnpm store path printed on stderr diff --git a/crates/socket-patch-cli/tests/e2e_socket_yml_policy.rs b/crates/socket-patch-cli/tests/e2e_socket_yml_policy.rs index 50018d6a9..9a02495b9 100644 --- a/crates/socket-patch-cli/tests/e2e_socket_yml_policy.rs +++ b/crates/socket-patch-cli/tests/e2e_socket_yml_policy.rs @@ -61,7 +61,11 @@ impl Patch { "low" => 3, _ => 4, }; - self.severities.iter().copied().min_by_key(|s| rank(s)).unwrap_or("unknown") + self.severities + .iter() + .copied() + .min_by_key(|s| rank(s)) + .unwrap_or("unknown") } } @@ -200,7 +204,9 @@ async fn mount_api(server: &MockServer, patches: Vec) { .await; let detail_map = by_purl.clone(); Mock::given(method("GET")) - .and(path_regex(format!("^/v0/orgs/{ORG}/patches/by-package/.+$"))) + .and(path_regex(format!( + "^/v0/orgs/{ORG}/patches/by-package/.+$" + ))) .respond_with(move |req: &Request| { let raw = req.url.path().rsplit('/').next().unwrap(); let purl = percent_decode(raw); @@ -216,11 +222,15 @@ async fn mount_api(server: &MockServer, patches: Vec) { }) }) .collect(); - ResponseTemplate::new(200).set_body_json(json!({"patches": list, "canAccessPaidPatches": false})) + ResponseTemplate::new(200) + .set_body_json(json!({"patches": list, "canAccessPaidPatches": false})) }) .mount(server) .await; - let by_uuid: BTreeMap = patches.iter().map(|p| (p.uuid.to_string(), p.clone())).collect(); + let by_uuid: BTreeMap = patches + .iter() + .map(|p| (p.uuid.to_string(), p.clone())) + .collect(); let refs = by_uuid.clone(); Mock::given(method("POST")) .and(path(format!("/v0/orgs/{ORG}/patches/package"))) @@ -277,8 +287,10 @@ fn write_npm_root(dir: &Path, deps: &[&str]) { let dep_map: BTreeMap<&str, &str> = deps.iter().map(|d| (*d, "1.0.0")).collect(); std::fs::write( dir.join("package.json"), - serde_json::to_string_pretty(&json!({"name": "consumer", "version": "0.0.0", "dependencies": dep_map})) - .unwrap(), + serde_json::to_string_pretty( + &json!({"name": "consumer", "version": "0.0.0", "dependencies": dep_map}), + ) + .unwrap(), ) .unwrap(); let mut packages = serde_json::Map::new(); @@ -289,7 +301,11 @@ fn write_npm_root(dir: &Path, deps: &[&str]) { for name in deps { let pkg = dir.join("node_modules").join(name); std::fs::create_dir_all(&pkg).unwrap(); - std::fs::write(pkg.join("package.json"), format!(r#"{{ "name": "{name}", "version": "1.0.0" }}"#)).unwrap(); + std::fs::write( + pkg.join("package.json"), + format!(r#"{{ "name": "{name}", "version": "1.0.0" }}"#), + ) + .unwrap(); std::fs::write(pkg.join("index.js"), orig_index(name)).unwrap(); packages.insert( format!("node_modules/{name}"), @@ -304,12 +320,20 @@ fn write_npm_root(dir: &Path, deps: &[&str]) { "name": "consumer", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": packages }); - std::fs::write(dir.join("package-lock.json"), serde_json::to_string_pretty(&lock).unwrap() + "\n").unwrap(); + std::fs::write( + dir.join("package-lock.json"), + serde_json::to_string_pretty(&lock).unwrap() + "\n", + ) + .unwrap(); } fn write_gem(dir: &Path, name: &str, version: &str) { - std::fs::create_dir_all(dir.join("vendor/bundle/ruby/3.0.0/gems").join(format!("{name}-{version}")).join("lib")) - .unwrap(); + std::fs::create_dir_all( + dir.join("vendor/bundle/ruby/3.0.0/gems") + .join(format!("{name}-{version}")) + .join("lib"), + ) + .unwrap(); } /// The monorepo: `services/web` (alpha, beta, left-pad + a gem), @@ -349,7 +373,11 @@ impl Repo { if entry.file_type().unwrap().is_dir() { walk(&path, root, out); } else { - let rel = path.strip_prefix(root).unwrap().to_string_lossy().into_owned(); + let rel = path + .strip_prefix(root) + .unwrap() + .to_string_lossy() + .into_owned(); out.insert(rel, std::fs::read(&path).unwrap()); } } @@ -369,7 +397,8 @@ fn run_cli(cwd: &Path, args: &[&str], env: &[(&str, &str)]) -> (i32, String, Str cmd.env_remove(key); } } - cmd.env_remove("GIT_CEILING_DIRECTORIES").env_remove("VIRTUAL_ENV"); + cmd.env_remove("GIT_CEILING_DIRECTORIES") + .env_remove("VIRTUAL_ENV"); cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); // The fixture's hosted pins name this origin; it makes them recorded. cmd.env("SOCKET_PATCH_SERVER_URL", "http://patch.test"); @@ -383,7 +412,8 @@ fn run_cli(cwd: &Path, args: &[&str], env: &[(&str, &str)]) -> (i32, String, Str ] { cmd.env(var, &absent); } - cmd.env("NPM_CONFIG_ALLOW_REMOTE", "").env("npm_config_allow_remote", ""); + cmd.env("NPM_CONFIG_ALLOW_REMOTE", "") + .env("npm_config_allow_remote", ""); for (k, v) in env { cmd.env(k, v); } @@ -418,8 +448,9 @@ fn scan_json(cwd: &Path, api: &str, extra: &[&str], env: &[(&str, &str)]) -> (i3 let mut args = vec!["--json"]; args.extend_from_slice(extra); let (code, stdout, stderr) = scan(cwd, api, &args, env); - let doc: Value = serde_json::from_str(&stdout) - .unwrap_or_else(|e| panic!("stdout must be JSON ({e})\nstdout=\n{stdout}\nstderr=\n{stderr}")); + let doc: Value = serde_json::from_str(&stdout).unwrap_or_else(|e| { + panic!("stdout must be JSON ({e})\nstdout=\n{stdout}\nstderr=\n{stderr}") + }); (code, doc) } @@ -428,7 +459,12 @@ fn filtered(doc: &Value) -> Vec<(Option, String)> { .as_array() .unwrap() .iter() - .map(|f| (f["purl"].as_str().map(str::to_string), f["reason"].as_str().unwrap().to_string())) + .map(|f| { + ( + f["purl"].as_str().map(str::to_string), + f["reason"].as_str().unwrap().to_string(), + ) + }) .collect() } @@ -444,7 +480,11 @@ fn filtered_reason<'a>(doc: &'a Value, purl: &str) -> &'a Value { fn warning_codes(doc: &Value) -> Vec { doc["warnings"] .as_array() - .map(|w| w.iter().filter_map(|e| e["code"].as_str().map(str::to_string)).collect()) + .map(|w| { + w.iter() + .filter_map(|e| e["code"].as_str().map(str::to_string)) + .collect() + }) .unwrap_or_default() } @@ -464,16 +504,28 @@ async fn hosted_filters_by_ecosystem_package_and_severity() { assert_eq!(code, 0, "{doc:#}"); let lock = repo.lock("services/web"); - assert!(lock.contains(&P_ALPHA.hosted_url()), "alpha is patched:\n{lock}"); - assert!(!lock.contains(P_BETA.uuid), "beta is below the floor:\n{lock}"); - assert!(!lock.contains(P_LEFTPAD.uuid), "left-pad is ignored:\n{lock}"); + assert!( + lock.contains(&P_ALPHA.hosted_url()), + "alpha is patched:\n{lock}" + ); + assert!( + !lock.contains(P_BETA.uuid), + "beta is below the floor:\n{lock}" + ); + assert!( + !lock.contains(P_LEFTPAD.uuid), + "left-pad is ignored:\n{lock}" + ); let policy = &doc["policy"]; assert_eq!(policy["source"], "file"); assert_eq!(policy["path"], "socket.yml"); assert_eq!(policy["sha256"].as_str().unwrap().len(), 64); assert_eq!(policy["enabled"], true); - assert_eq!(policy["minSeverity"], json!({"value": "high", "source": "file"})); + assert_eq!( + policy["minSeverity"], + json!({"value": "high", "source": "file"}) + ); let beta = filtered_reason(&doc, "pkg:npm/beta@1.0.0"); assert_eq!(beta["reason"], "policy_severity"); assert_eq!(beta["detail"], "low < high"); @@ -481,8 +533,15 @@ async fn hosted_filters_by_ecosystem_package_and_severity() { assert_eq!(beta["project"], "services/web"); let left_pad = filtered_reason(&doc, "pkg:npm/left-pad@1.0.0"); assert_eq!(left_pad["reason"], "policy_package_ignored"); - assert_eq!(left_pad["uuid"], Value::Null, "filtered before any patch lookup"); - assert_eq!(left_pad["detail"], "pkg:npm/left-pad (patches.ignorePackages)"); + assert_eq!( + left_pad["uuid"], + Value::Null, + "filtered before any patch lookup" + ); + assert_eq!( + left_pad["detail"], + "pkg:npm/left-pad (patches.ignorePackages)" + ); let rack = filtered_reason(&doc, "pkg:gem/rack@1.0.0"); assert_eq!(rack["reason"], "policy_ecosystem"); assert_eq!(policy["counts"]["filtered"], 3); @@ -492,7 +551,10 @@ async fn hosted_filters_by_ecosystem_package_and_severity() { for r in &reqs { if r.url.path().ends_with("/patches/batch") { let body = String::from_utf8_lossy(&r.body); - assert!(!body.contains("left-pad") && !body.contains("rack"), "{body}"); + assert!( + !body.contains("left-pad") && !body.contains("rack"), + "{body}" + ); } } assert_eq!(doc["redirect"]["redirected"], 1, "{:#}", doc["redirect"]); @@ -503,13 +565,27 @@ async fn hosted_filters_by_ecosystem_package_and_severity() { async fn hosted_dry_run_makes_the_same_decisions_and_writes_nothing() { let server = MockServer::start().await; mount_api(&server, catalog()).await; - let repo = Repo::new(Some("version: 2\npatches:\n minSeverity: high\n ecosystems: [npm]\n")); + let repo = Repo::new(Some( + "version: 2\npatches:\n minSeverity: high\n ecosystems: [npm]\n", + )); let before = repo.snapshot(); - let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &["--dry-run"], &[]); + let (code, doc) = scan_json( + &repo.dir("services/web"), + &server.uri(), + &["--dry-run"], + &[], + ); assert_eq!(code, 0, "{doc:#}"); assert_eq!(repo.snapshot(), before, "a dry run changes no bytes"); - assert_eq!(doc["redirect"]["redirected"], 2, "alpha and left-pad: {:#}", doc["redirect"]); - assert_eq!(filtered_reason(&doc, "pkg:npm/beta@1.0.0")["reason"], "policy_severity"); + assert_eq!( + doc["redirect"]["redirected"], 2, + "alpha and left-pad: {:#}", + doc["redirect"] + ); + assert_eq!( + filtered_reason(&doc, "pkg:npm/beta@1.0.0")["reason"], + "policy_severity" + ); } #[tokio::test] @@ -517,14 +593,24 @@ async fn hosted_dry_run_makes_the_same_decisions_and_writes_nothing() { async fn path_globs_apply_default_ignores_and_ignore_paths_human() { let server = MockServer::start().await; mount_api(&server, catalog()).await; - let repo = Repo::new(Some("version: 2\npatches:\n ignorePaths: [\"/services/legacy/\"]\n")); + let repo = Repo::new(Some( + "version: 2\npatches:\n ignorePaths: [\"/services/legacy/\"]\n", + )); let legacy = repo.lock("services/legacy"); let test_lock = repo.lock("services/test"); let (code, stdout, stderr) = scan(&repo.root, &server.uri(), &["services/*"], &[]); assert_eq!(code, 0, "stdout:\n{stdout}\nstderr:\n{stderr}"); assert!(repo.lock("services/web").contains(&P_ALPHA.hosted_url())); - assert_eq!(repo.lock("services/legacy"), legacy, "ignored by patches.ignorePaths"); - assert_eq!(repo.lock("services/test"), test_lock, "a discovered test/ root is a built-in ignore"); + assert_eq!( + repo.lock("services/legacy"), + legacy, + "ignored by patches.ignorePaths" + ); + assert_eq!( + repo.lock("services/test"), + test_lock, + "a discovered test/ root is a built-in ignore" + ); assert!(stdout.contains("Policy (socket.yml)"), "{stdout}"); // Named literally, the test/ root is explicit: defaults do not apply. @@ -538,7 +624,9 @@ async fn path_globs_apply_default_ignores_and_ignore_paths_human() { async fn include_paths_limit_roots() { let server = MockServer::start().await; mount_api(&server, catalog()).await; - let repo = Repo::new(Some("version: 2\npatches:\n includePaths: [\"/services/legacy/\"]\n")); + let repo = Repo::new(Some( + "version: 2\npatches:\n includePaths: [\"/services/legacy/\"]\n", + )); let web = repo.lock("services/web"); let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &[], &[]); assert_eq!(code, 0, "{doc:#}"); @@ -571,7 +659,10 @@ async fn invalid_file_fails_closed_before_any_request_or_write() { assert!(message.contains("--no-socket-yml"), "{message}"); assert!(doc.get("policy").is_none()); assert_eq!(repo.snapshot(), before); - assert!(server.received_requests().await.unwrap().is_empty(), "no request before the policy loads"); + assert!( + server.received_requests().await.unwrap().is_empty(), + "no request before the policy loads" + ); // Human output names the code on stderr, same exit code. let (code, _, stderr) = scan(&repo.dir("services/web"), &server.uri(), &[], &[]); @@ -579,10 +670,20 @@ async fn invalid_file_fails_closed_before_any_request_or_write() { assert!(stderr.contains("socket_yml_invalid"), "{stderr}"); // --no-socket-yml (and its env var) skips the file. - let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &["--no-socket-yml", "--dry-run"], &[]); + let (code, doc) = scan_json( + &repo.dir("services/web"), + &server.uri(), + &["--no-socket-yml", "--dry-run"], + &[], + ); assert_eq!(code, 0, "{doc:#}"); assert_eq!(doc["policy"]["source"], "bypassed"); - let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &["--dry-run"], &[("SOCKET_NO_SOCKET_YML", "1")]); + let (code, doc) = scan_json( + &repo.dir("services/web"), + &server.uri(), + &["--dry-run"], + &[("SOCKET_NO_SOCKET_YML", "1")], + ); assert_eq!(code, 0, "{doc:#}"); assert_eq!(doc["policy"]["source"], "bypassed"); } @@ -593,7 +694,11 @@ async fn both_files_disagreeing_is_ambiguous() { let server = MockServer::start().await; mount_api(&server, catalog()).await; let repo = Repo::new(Some("version: 2\npatches:\n maxNewPatches: 1\n")); - std::fs::write(repo.root.join("socket.yaml"), "version: 2\npatches:\n maxNewPatches: 2\n").unwrap(); + std::fs::write( + repo.root.join("socket.yaml"), + "version: 2\npatches:\n maxNewPatches: 2\n", + ) + .unwrap(); let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &[], &[]); assert_eq!(code, 1); assert_eq!(doc["errorCode"], "socket_yml_ambiguous"); @@ -606,26 +711,66 @@ async fn severity_flag_and_env_override_the_file() { mount_api(&server, catalog()).await; let repo = Repo::new(Some("version: 2\npatches:\n minSeverity: high\n")); let web = repo.dir("services/web"); - let (code, doc) = scan_json(&web, &server.uri(), &["--dry-run", "--min-severity", "none"], &[]); + let (code, doc) = scan_json( + &web, + &server.uri(), + &["--dry-run", "--min-severity", "none"], + &[], + ); assert_eq!(code, 0, "{doc:#}"); - assert_eq!(doc["policy"]["minSeverity"], json!({"value": null, "source": "flag"})); - assert_eq!(doc["redirect"]["redirected"], 3, "beta too once the floor is lifted"); + assert_eq!( + doc["policy"]["minSeverity"], + json!({"value": null, "source": "flag"}) + ); + assert_eq!( + doc["redirect"]["redirected"], 3, + "beta too once the floor is lifted" + ); - let (code, doc) = scan_json(&web, &server.uri(), &["--dry-run"], &[("SOCKET_MIN_SEVERITY", "critical")]); + let (code, doc) = scan_json( + &web, + &server.uri(), + &["--dry-run"], + &[("SOCKET_MIN_SEVERITY", "critical")], + ); assert_eq!(code, 0, "{doc:#}"); - assert_eq!(doc["policy"]["minSeverity"], json!({"value": "critical", "source": "env"})); + assert_eq!( + doc["policy"]["minSeverity"], + json!({"value": "critical", "source": "env"}) + ); assert_eq!(doc["redirect"]["redirected"], 1); // The flag beats the env; an empty env value is unset. - let (_, doc) = scan_json(&web, &server.uri(), &["--dry-run", "--min-severity", "moderate"], &[("SOCKET_MIN_SEVERITY", "critical")]); - assert_eq!(doc["policy"]["minSeverity"], json!({"value": "medium", "source": "flag"})); - let (_, doc) = scan_json(&web, &server.uri(), &["--dry-run"], &[("SOCKET_MIN_SEVERITY", "")]); - assert_eq!(doc["policy"]["minSeverity"], json!({"value": "high", "source": "file"})); + let (_, doc) = scan_json( + &web, + &server.uri(), + &["--dry-run", "--min-severity", "moderate"], + &[("SOCKET_MIN_SEVERITY", "critical")], + ); + assert_eq!( + doc["policy"]["minSeverity"], + json!({"value": "medium", "source": "flag"}) + ); + let (_, doc) = scan_json( + &web, + &server.uri(), + &["--dry-run"], + &[("SOCKET_MIN_SEVERITY", "")], + ); + assert_eq!( + doc["policy"]["minSeverity"], + json!({"value": "high", "source": "file"}) + ); // Malformed values are usage errors. let (code, _, stderr) = scan(&web, &server.uri(), &["--min-severity", "severe"], &[]); assert_eq!(code, 2, "{stderr}"); - let (code, _, stderr) = scan(&web, &server.uri(), &[], &[("SOCKET_MIN_SEVERITY", "severe")]); + let (code, _, stderr) = scan( + &web, + &server.uri(), + &[], + &[("SOCKET_MIN_SEVERITY", "severe")], + ); assert_eq!(code, 2, "{stderr}"); assert!(stderr.contains("SOCKET_MIN_SEVERITY"), "{stderr}"); } @@ -643,12 +788,20 @@ async fn narrowing_after_a_hosted_patch_leaves_the_pin_byte_identical() { assert!(pinned.contains(&P_ALPHA.hosted_url())); // A newer merged patch appears, and the repo now ignores alpha. - std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n ignorePackages: [alpha]\n").unwrap(); + std::fs::write( + repo.root.join("socket.yml"), + "version: 2\npatches:\n ignorePackages: [alpha]\n", + ) + .unwrap(); server.reset().await; mount_api(&server, vec![P_ALPHA, P_ALPHA_MERGED_NEW]).await; let (code, doc) = scan_json(&web, &server.uri(), &[], &[]); assert_eq!(code, 0, "{doc:#}"); - assert_eq!(repo.lock("services/web"), pinned, "retained: not upgraded, not removed"); + assert_eq!( + repo.lock("services/web"), + pinned, + "retained: not upgraded, not removed" + ); let retained = &doc["policy"]["retained"][0]; assert_eq!(retained["purl"], "pkg:npm/alpha@1.0.0"); assert_eq!(retained["recordedUuid"], P_ALPHA.uuid); @@ -666,7 +819,11 @@ async fn narrowing_after_a_hosted_patch_leaves_the_pin_byte_identical() { let (code, doc) = scan_json(&web, &server.uri(), &[], &[]); assert_eq!(code, 0, "{doc:#}"); assert_eq!(repo.lock("services/web"), pinned, "{yml}"); - assert_eq!(doc["policy"]["retained"][0]["purl"], "pkg:npm/alpha@1.0.0", "{yml}: {:#}", doc["policy"]); + assert_eq!( + doc["policy"]["retained"][0]["purl"], "pkg:npm/alpha@1.0.0", + "{yml}: {:#}", + doc["policy"] + ); } } @@ -681,9 +838,15 @@ async fn enabled_false_reports_and_writes_nothing() { assert_eq!(code, 0, "{doc:#}"); assert_eq!(repo.snapshot(), before); assert_eq!(doc["policy"]["enabled"], false); - assert!(warning_codes(&doc).contains(&"patches_disabled".to_string()), "{doc:#}"); + assert!( + warning_codes(&doc).contains(&"patches_disabled".to_string()), + "{doc:#}" + ); let reasons: Vec = filtered(&doc).into_iter().map(|(_, r)| r).collect(); - assert!(!reasons.is_empty() && reasons.iter().all(|r| r == "policy_disabled"), "{reasons:?}"); + assert!( + !reasons.is_empty() && reasons.iter().all(|r| r == "policy_disabled"), + "{reasons:?}" + ); assert_eq!(doc["redirect"]["redirected"], 0); } @@ -692,7 +855,9 @@ async fn enabled_false_reports_and_writes_nothing() { async fn report_only_json_fails_when_every_detail_query_fails() { let server = MockServer::start().await; Mock::given(method("GET")) - .and(path_regex(format!("^/v0/orgs/{ORG}/patches/by-package/.+$"))) + .and(path_regex(format!( + "^/v0/orgs/{ORG}/patches/by-package/.+$" + ))) .respond_with(ResponseTemplate::new(500)) .with_priority(1) .mount(&server) @@ -705,7 +870,10 @@ async fn report_only_json_fails_when_every_detail_query_fails() { assert_eq!(code, 1, "{doc:#}"); assert_eq!(doc["status"], "error", "{doc:#}"); assert!( - doc["error"].as_str().unwrap_or_default().contains("patch-detail queries failed"), + doc["error"] + .as_str() + .unwrap_or_default() + .contains("patch-detail queries failed"), "{doc:#}" ); assert_eq!(repo.snapshot(), before); @@ -726,7 +894,11 @@ async fn recorded_merge_below_the_floor_is_kept_until_a_more_severe_patch_is_ava "the only available patch is pinned:\n{pinned}" ); - std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n minSeverity: high\n").unwrap(); + std::fs::write( + repo.root.join("socket.yml"), + "version: 2\npatches:\n minSeverity: high\n", + ) + .unwrap(); let (code, doc) = scan_json(&web, &server.uri(), &[], &[]); assert_eq!(code, 0, "{doc:#}"); assert_eq!( @@ -778,11 +950,17 @@ async fn floor_with_nothing_admitted_reports_the_withheld_patch() { let (code, stdout, stderr) = scan(&web, &server.uri(), &[], &[]); assert_eq!(code, 0, "{stdout}\n{stderr}"); assert_eq!(repo.lock("services/web"), lock); - assert!(stdout.contains("Policy (socket.yml): 1 skipped by filters"), "{stdout}"); + assert!( + stdout.contains("Policy (socket.yml): 1 skipped by filters"), + "{stdout}" + ); // Only critical/high are named without --verbose. assert!(!stdout.contains("skipped beta"), "{stdout}"); let (_, stdout, _) = scan(&web, &server.uri(), &["--verbose"], &[]); - assert!(stdout.contains("skipped pkg:npm/beta@1.0.0 (low): low < critical"), "{stdout}"); + assert!( + stdout.contains("skipped pkg:npm/beta@1.0.0 (low): low < critical"), + "{stdout}" + ); } #[tokio::test] @@ -793,9 +971,17 @@ async fn path_outside_the_repo_is_a_usage_error() { let repo = Repo::new(None); let outside = repo.root.parent().unwrap().join("elsewhere"); write_npm_root(&outside, &["alpha"]); - let (code, _, stderr) = scan(&repo.dir("services"), &server.uri(), &["web", "../../elsewhere"], &[]); + let (code, _, stderr) = scan( + &repo.dir("services"), + &server.uri(), + &["web", "../../elsewhere"], + &[], + ); assert_eq!(code, 2, "{stderr}"); - assert!(stderr.contains("is outside") && stderr.contains("run one scan per repository"), "{stderr}"); + assert!( + stderr.contains("is outside") && stderr.contains("run one scan per repository"), + "{stderr}" + ); } #[tokio::test] @@ -803,7 +989,9 @@ async fn path_outside_the_repo_is_a_usage_error() { async fn project_ignore_paths_is_honored_without_a_patches_block() { let server = MockServer::start().await; mount_api(&server, catalog()).await; - let repo = Repo::new(Some("version: 2\nprojectIgnorePaths:\n - \"services/legacy/**\"\n")); + let repo = Repo::new(Some( + "version: 2\nprojectIgnorePaths:\n - \"services/legacy/**\"\n", + )); let legacy = repo.lock("services/legacy"); let (code, doc) = scan_json(&repo.dir("services/legacy"), &server.uri(), &[], &[]); assert_eq!(code, 0, "{doc:#}"); @@ -813,10 +1001,22 @@ async fn project_ignore_paths_is_honored_without_a_patches_block() { assert_eq!(entry["detail"], "services/legacy/** (projectIgnorePaths)"); // A malformed projectIgnorePaths without a patches block only warns. - std::fs::write(repo.root.join("socket.yml"), "version: 2\nprojectIgnorePaths: {a: 1}\n").unwrap(); - let (code, doc) = scan_json(&repo.dir("services/legacy"), &server.uri(), &["--dry-run"], &[]); + std::fs::write( + repo.root.join("socket.yml"), + "version: 2\nprojectIgnorePaths: {a: 1}\n", + ) + .unwrap(); + let (code, doc) = scan_json( + &repo.dir("services/legacy"), + &server.uri(), + &["--dry-run"], + &[], + ); assert_eq!(code, 0, "{doc:#}"); - assert!(warning_codes(&doc).contains(&"socket_yml_ignored_value".to_string()), "{doc:#}"); + assert!( + warning_codes(&doc).contains(&"socket_yml_ignored_value".to_string()), + "{doc:#}" + ); } // --------------------------------------------------------------------------- @@ -845,14 +1045,18 @@ async fn agent_mode_applies_only_admitted_patches() { let (code, doc) = scan_json(&web, &server.uri(), &["--mode", "agent"], &[]); assert_eq!(code, 0, "{doc:#}"); let manifest: Value = - serde_json::from_str(&std::fs::read_to_string(web.join(".socket/manifest.json")).unwrap()).unwrap(); + serde_json::from_str(&std::fs::read_to_string(web.join(".socket/manifest.json")).unwrap()) + .unwrap(); let keys: Vec<&String> = manifest["patches"].as_object().unwrap().keys().collect(); assert_eq!(keys, ["pkg:npm/alpha@1.0.0"]); assert_eq!( std::fs::read_to_string(web.join("node_modules/alpha/index.js")).unwrap(), patched_index("alpha") ); - assert_eq!(std::fs::read_to_string(web.join("node_modules/beta/index.js")).unwrap(), orig_index("beta")); + assert_eq!( + std::fs::read_to_string(web.join("node_modules/beta/index.js")).unwrap(), + orig_index("beta") + ); } #[tokio::test] @@ -867,13 +1071,23 @@ async fn agent_mode_retains_a_recorded_patch_the_policy_now_excludes() { let manifest_before = std::fs::read(web.join(".socket/manifest.json")).unwrap(); let installed_before = std::fs::read(web.join("node_modules/alpha/index.js")).unwrap(); - std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n ecosystems: [pypi]\n").unwrap(); + std::fs::write( + repo.root.join("socket.yml"), + "version: 2\npatches:\n ecosystems: [pypi]\n", + ) + .unwrap(); server.reset().await; mount_api(&server, vec![P_ALPHA, P_ALPHA_MERGED_NEW]).await; let (code, doc) = scan_json(&web, &server.uri(), &["--mode", "agent"], &[]); assert_eq!(code, 0, "{doc:#}"); - assert_eq!(std::fs::read(web.join(".socket/manifest.json")).unwrap(), manifest_before); - assert_eq!(std::fs::read(web.join("node_modules/alpha/index.js")).unwrap(), installed_before); + assert_eq!( + std::fs::read(web.join(".socket/manifest.json")).unwrap(), + manifest_before + ); + assert_eq!( + std::fs::read(web.join("node_modules/alpha/index.js")).unwrap(), + installed_before + ); assert_eq!(doc["policy"]["retained"][0]["reason"], "policy_ecosystem"); assert_eq!(doc["policy"]["retained"][0]["upgradeAvailable"], true); } @@ -889,7 +1103,12 @@ async fn vendored_dry_run_previews_only_admitted_patches() { mount_api(&server, catalog()).await; let repo = Repo::new(Some("version: 2\npatches:\n packages: [\"pkg:npm/beta\", \"pkg:npm/left-pad\"]\n minSeverity: medium\n")); let before = repo.snapshot(); - let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &["--mode", "vendored", "--dry-run"], &[]); + let (code, doc) = scan_json( + &repo.dir("services/web"), + &server.uri(), + &["--mode", "vendored", "--dry-run"], + &[], + ); assert_eq!(code, 0, "{doc:#}"); assert_eq!(repo.snapshot(), before); let previewed: Vec<&str> = doc["vendor"]["patches"] @@ -899,8 +1118,14 @@ async fn vendored_dry_run_previews_only_admitted_patches() { .filter_map(|p| p["purl"].as_str()) .collect(); assert_eq!(previewed, ["pkg:npm/left-pad@1.0.0"], "{doc:#}"); - assert_eq!(filtered_reason(&doc, "pkg:npm/alpha@1.0.0")["reason"], "policy_package_not_listed"); - assert_eq!(filtered_reason(&doc, "pkg:npm/beta@1.0.0")["reason"], "policy_severity"); + assert_eq!( + filtered_reason(&doc, "pkg:npm/alpha@1.0.0")["reason"], + "policy_package_not_listed" + ); + assert_eq!( + filtered_reason(&doc, "pkg:npm/beta@1.0.0")["reason"], + "policy_severity" + ); } // --------------------------------------------------------------------------- @@ -932,9 +1157,16 @@ async fn get_bypasses_the_policy_with_a_warning() { let (code, stdout, stderr) = run_cli(&web, &args, &[]); assert_eq!(code, 0, "stdout:\n{stdout}\nstderr:\n{stderr}"); let doc: Value = serde_json::from_str(&stdout).unwrap(); - let warnings: Vec<&str> = doc["warnings"].as_array().unwrap().iter().filter_map(Value::as_str).collect(); + let warnings: Vec<&str> = doc["warnings"] + .as_array() + .unwrap() + .iter() + .filter_map(Value::as_str) + .collect(); assert!( - warnings.iter().any(|w| w.starts_with("(policy_bypassed)") && w.contains("alpha")), + warnings + .iter() + .any(|w| w.starts_with("(policy_bypassed)") && w.contains("alpha")), "{doc:#}" ); @@ -960,30 +1192,65 @@ async fn agent_mode_honors_path_filters_and_keeps_the_prune_universe() { // The root is excluded by path: nothing selected, and a --sync (agent // + prune) still judges the full crawl, so no entry is pruned. - std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n includePaths: [\"/services/legacy/\"]\n").unwrap(); + std::fs::write( + repo.root.join("socket.yml"), + "version: 2\npatches:\n includePaths: [\"/services/legacy/\"]\n", + ) + .unwrap(); let (code, doc) = scan_json(&web, &server.uri(), &["--sync"], &[]); assert_eq!(code, 0, "{doc:#}"); - assert_eq!(std::fs::read(web.join(".socket/manifest.json")).unwrap(), manifest_before); + assert_eq!( + std::fs::read(web.join(".socket/manifest.json")).unwrap(), + manifest_before + ); assert_eq!(doc["policy"]["filtered"][0]["purl"], Value::Null); - assert_eq!(doc["policy"]["filtered"][0]["reason"], "policy_path_not_included"); - assert_eq!(doc["policy"]["counts"]["retained"], 2, "{:#}", doc["policy"]); - assert_eq!(doc["gc"]["removed"].as_array().map_or(0, Vec::len), 0, "{:#}", doc["gc"]); + assert_eq!( + doc["policy"]["filtered"][0]["reason"], + "policy_path_not_included" + ); + assert_eq!( + doc["policy"]["counts"]["retained"], 2, + "{:#}", + doc["policy"] + ); + assert_eq!( + doc["gc"]["removed"].as_array().map_or(0, Vec::len), + 0, + "{:#}", + doc["gc"] + ); // A narrower ecosystem list under --sync prunes nothing either. - std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n ecosystems: [pypi]\n").unwrap(); + std::fs::write( + repo.root.join("socket.yml"), + "version: 2\npatches:\n ecosystems: [pypi]\n", + ) + .unwrap(); let (code, doc) = scan_json(&web, &server.uri(), &["--sync"], &[]); assert_eq!(code, 0, "{doc:#}"); - assert_eq!(std::fs::read(web.join(".socket/manifest.json")).unwrap(), manifest_before); + assert_eq!( + std::fs::read(web.join(".socket/manifest.json")).unwrap(), + manifest_before + ); // patches.enabled: false skips the GC entirely. std::fs::remove_dir_all(web.join("node_modules/beta")).unwrap(); - let pkg_lock = repo.lock("services/web").replace("\"node_modules/beta\"", "\"node_modules/gone\""); + let pkg_lock = repo + .lock("services/web") + .replace("\"node_modules/beta\"", "\"node_modules/gone\""); std::fs::write(web.join("package-lock.json"), pkg_lock).unwrap(); - std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n enabled: false\n").unwrap(); + std::fs::write( + repo.root.join("socket.yml"), + "version: 2\npatches:\n enabled: false\n", + ) + .unwrap(); let (code, doc) = scan_json(&web, &server.uri(), &["--sync"], &[]); assert_eq!(code, 0, "{doc:#}"); assert!(doc.get("gc").is_none(), "{doc:#}"); - assert_eq!(std::fs::read(web.join(".socket/manifest.json")).unwrap(), manifest_before); + assert_eq!( + std::fs::read(web.join(".socket/manifest.json")).unwrap(), + manifest_before + ); } #[tokio::test] @@ -997,29 +1264,53 @@ async fn narrowing_after_vendoring_leaves_the_vendored_package_byte_identical() let before = compute_git_sha256_from_bytes(orig_index("alpha").as_bytes()); let after = compute_git_sha256_from_bytes(patched_index("alpha").as_bytes()); std::fs::create_dir_all(web.join(".socket/blobs")).unwrap(); - std::fs::write(web.join(".socket/blobs").join(&after), patched_index("alpha")).unwrap(); + std::fs::write( + web.join(".socket/blobs").join(&after), + patched_index("alpha"), + ) + .unwrap(); let manifest = json!({"patches": {P_ALPHA.purl(): { "uuid": P_ALPHA.uuid, "exportedAt": "2026-01-01T00:00:00Z", "files": {"package/index.js": {"beforeHash": before, "afterHash": after}}, "vulnerabilities": {}, "description": "d", "license": "MIT", "tier": "free" }}}); - std::fs::write(web.join(".socket/manifest.json"), serde_json::to_vec_pretty(&manifest).unwrap()).unwrap(); + std::fs::write( + web.join(".socket/manifest.json"), + serde_json::to_vec_pretty(&manifest).unwrap(), + ) + .unwrap(); let fixture = prebuilt_common::Server::project(&web); let (code, stdout, stderr) = run_cli( &web, &["vendor", "--json", "--cwd", web.to_str().unwrap()], - &[("SOCKET_VENDOR_URL", &fixture.uri), ("SOCKET_PATCH_SERVER_URL", &fixture.uri)], + &[ + ("SOCKET_VENDOR_URL", &fixture.uri), + ("SOCKET_PATCH_SERVER_URL", &fixture.uri), + ], ); assert_eq!(code, 0, "vendor fixture: {stdout}\n{stderr}"); - assert!(repo.lock("services/web").contains(".socket/vendor/"), "vendored lock"); + assert!( + repo.lock("services/web").contains(".socket/vendor/"), + "vendored lock" + ); let snapshot = repo.snapshot(); - std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n ignorePackages: [\"pkg:npm/alpha\"]\n").unwrap(); + std::fs::write( + repo.root.join("socket.yml"), + "version: 2\npatches:\n ignorePackages: [\"pkg:npm/alpha\"]\n", + ) + .unwrap(); let (code, doc) = scan_json(&web, &server.uri(), &["--mode", "vendored"], &[]); assert_eq!(code, 0, "{doc:#}"); let mut after_scan = repo.snapshot(); after_scan.remove("socket.yml"); - assert_eq!(after_scan, snapshot, "the vendored package, its lock wiring and ledger stay byte-identical"); - assert_eq!(doc["policy"]["retained"][0]["purl"], "pkg:npm/alpha@1.0.0", "{:#}", doc["policy"]); + assert_eq!( + after_scan, snapshot, + "the vendored package, its lock wiring and ledger stay byte-identical" + ); + assert_eq!( + doc["policy"]["retained"][0]["purl"], "pkg:npm/alpha@1.0.0", + "{:#}", + doc["policy"] + ); } - diff --git a/crates/socket-patch-cli/tests/e2e_vex_lockfile/common_selftest.rs b/crates/socket-patch-cli/tests/e2e_vex_lockfile/common_selftest.rs index 83a8de749..0dd0998af 100644 --- a/crates/socket-patch-cli/tests/e2e_vex_lockfile/common_selftest.rs +++ b/crates/socket-patch-cli/tests/e2e_vex_lockfile/common_selftest.rs @@ -152,7 +152,11 @@ fn committed_pre_v5_ledger_lets_a_hosted_pin_attest_offline() { ); } api.assert_no_requests(); - assert_eq!(std::fs::read(&ledger).unwrap(), before, "vex never rewrites it"); + assert_eq!( + std::fs::read(&ledger).unwrap(), + before, + "vex never rewrites it" + ); let other = "0b0b0b0b-0b0b-4b0b-8b0b-0b0b0b0b0b0b"; let mut stale = left_pad_view(); diff --git a/crates/socket-patch-cli/tests/e2e_yarn4_pnpm_linker_build.rs b/crates/socket-patch-cli/tests/e2e_yarn4_pnpm_linker_build.rs index ce5d1144b..ddadbb45d 100644 --- a/crates/socket-patch-cli/tests/e2e_yarn4_pnpm_linker_build.rs +++ b/crates/socket-patch-cli/tests/e2e_yarn4_pnpm_linker_build.rs @@ -571,9 +571,9 @@ async fn yarn4_pnpm_linker_hosted_redirect_fresh_checkout_installs_patched_bytes let root_pkg = std::fs::read_to_string(proj.join("package.json")).unwrap(); let root_pkg: serde_json::Value = serde_json::from_str(&root_pkg).unwrap(); assert!( - root_pkg["resolutions"] - .as_object() - .is_some_and(|r| r.iter().any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) + root_pkg["resolutions"].as_object().is_some_and(|r| r + .iter() + .any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) && v.as_str() == Some(hosted_url.as_str()))), "package.json must route {DEP} to the hosted tarball: {root_pkg}" ); @@ -596,7 +596,10 @@ async fn yarn4_pnpm_linker_hosted_redirect_fresh_checkout_installs_patched_bytes serde_json::from_slice(&std::fs::read(proj.join("package.json")).unwrap()).unwrap(); let before: serde_json::Value = serde_json::from_slice(&pkg_before).unwrap(); after.as_object_mut().unwrap().shift_remove("resolutions"); - assert_eq!(after, before, "the hosted pin only adds `resolutions` to package.json"); + assert_eq!( + after, before, + "the hosted pin only adds `resolutions` to package.json" + ); } eprintln!("HOSTED REWIRE OK"); @@ -625,7 +628,10 @@ async fn yarn4_pnpm_linker_hosted_redirect_fresh_checkout_installs_patched_bytes eprintln!("FRESH INSTALL + YARN NODE RESOLUTION OK"); // MANIFEST-LESS VEX over the hosted wiring (see `yarn_berry_common`). - let registry_state = [("yarn.lock", registry_lock), ("package.json", pkg_before.clone())]; + let registry_state = [ + ("yarn.lock", registry_lock), + ("package.json", pkg_before.clone()), + ]; let yarn = |cwd: &Path, args: &[&str], env: &[(&str, &str)]| corepack(cwd, yarn_berry(), args, env); let api_url = server.uri(); diff --git a/crates/socket-patch-cli/tests/e2e_yarn4_workspaces_build.rs b/crates/socket-patch-cli/tests/e2e_yarn4_workspaces_build.rs index d2aec0078..2794a4781 100644 --- a/crates/socket-patch-cli/tests/e2e_yarn4_workspaces_build.rs +++ b/crates/socket-patch-cli/tests/e2e_yarn4_workspaces_build.rs @@ -566,9 +566,9 @@ async fn yarn4_workspaces_hosted_redirect_rewires_member_dep_from_root_scan() { let root_pkg = std::fs::read_to_string(proj.join("package.json")).unwrap(); let root_pkg: serde_json::Value = serde_json::from_str(&root_pkg).unwrap(); assert!( - root_pkg["resolutions"] - .as_object() - .is_some_and(|r| r.iter().any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) + root_pkg["resolutions"].as_object().is_some_and(|r| r + .iter() + .any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) && v.as_str() == Some(hosted_url.as_str()))), "package.json must route {DEP} to the hosted tarball: {root_pkg}" ); @@ -596,7 +596,10 @@ async fn yarn4_workspaces_hosted_redirect_rewires_member_dep_from_root_scan() { serde_json::from_slice(&std::fs::read(proj.join("package.json")).unwrap()).unwrap(); let before: serde_json::Value = serde_json::from_slice(&root_pkg_before).unwrap(); after.as_object_mut().unwrap().shift_remove("resolutions"); - assert_eq!(after, before, "the hosted pin only adds `resolutions` to the root package.json"); + assert_eq!( + after, before, + "the hosted pin only adds `resolutions` to the root package.json" + ); } assert_eq!( std::fs::read(proj.join("packages/app/package.json")).unwrap(), @@ -630,7 +633,10 @@ async fn yarn4_workspaces_hosted_redirect_rewires_member_dep_from_root_scan() { eprintln!("FRESH INSTALL + MEMBER RESOLUTION OK"); // MANIFEST-LESS VEX over the hosted wiring (see `yarn_berry_common`). - let registry_state = [("yarn.lock", registry_lock), ("package.json", root_pkg_before.clone())]; + let registry_state = [ + ("yarn.lock", registry_lock), + ("package.json", root_pkg_before.clone()), + ]; let yarn = |cwd: &Path, args: &[&str], env: &[(&str, &str)]| corepack(cwd, yarn_berry(), args, env); let api_url = server.uri(); diff --git a/crates/socket-patch-cli/tests/get/get_edge_cases_e2e.rs b/crates/socket-patch-cli/tests/get/get_edge_cases_e2e.rs index e32b4ea97..6cdd44ef1 100644 --- a/crates/socket-patch-cli/tests/get/get_edge_cases_e2e.rs +++ b/crates/socket-patch-cli/tests/get/get_edge_cases_e2e.rs @@ -469,8 +469,16 @@ fn get_help_lists_all_identifier_flags() { ); } // Help text is for users: no implementation notes from the source. - for leak in ["value_parser", "parse_bool_flag", "No env binding", "locally- installed"] { - assert!(!stdout.contains(leak), "get --help leaks {leak:?}: {stdout}"); + for leak in [ + "value_parser", + "parse_bool_flag", + "No env binding", + "locally- installed", + ] { + assert!( + !stdout.contains(leak), + "get --help leaks {leak:?}: {stdout}" + ); } } diff --git a/crates/socket-patch-cli/tests/get/global_packages_e2e.rs b/crates/socket-patch-cli/tests/get/global_packages_e2e.rs index 25bdadd21..a86958fe8 100644 --- a/crates/socket-patch-cli/tests/get/global_packages_e2e.rs +++ b/crates/socket-patch-cli/tests/get/global_packages_e2e.rs @@ -211,7 +211,10 @@ fn assert_rollback_noop(stdout: &str) { r["skipped"], "package_not_installed", "a no-op rollback may carry only not-installed markers; envelope={v}" ); - assert!(r["path"].is_null(), "marker path must be null; envelope={v}"); + assert!( + r["path"].is_null(), + "marker path must be null; envelope={v}" + ); assert!( r.get("success").is_none() && r.get("error").is_none(), "markers carry no success/error keys; envelope={v}" diff --git a/crates/socket-patch-cli/tests/help_text_hygiene.rs b/crates/socket-patch-cli/tests/help_text_hygiene.rs index 9b3280e8a..467ed46c5 100644 --- a/crates/socket-patch-cli/tests/help_text_hygiene.rs +++ b/crates/socket-patch-cli/tests/help_text_hygiene.rs @@ -61,7 +61,11 @@ fn every_help_page_has_no_developer_notes() { names.extend(cmd.get_subcommands().map(|s| s.get_name().to_string())); let mut failures = Vec::new(); for name in &names { - let path: Vec<&str> = if name.is_empty() { vec![] } else { vec![name.as_str()] }; + let path: Vec<&str> = if name.is_empty() { + vec![] + } else { + vec![name.as_str()] + }; let text = long_help(&path); let found = leaks(&text); if !found.is_empty() { @@ -147,7 +151,9 @@ fn vex_product_list_renders_one_item_per_line() { fn root_command_list_uses_the_verb_form() { let text = long_help(&[]); assert!( - text.contains("Undo patches: restore original files and unwind hosted or vendored lockfile wiring"), + text.contains( + "Undo patches: restore original files and unwind hosted or vendored lockfile wiring" + ), "{text}" ); assert!(!text.contains("Rollback patches"), "{text}"); @@ -258,11 +264,24 @@ fn short_help_lists_about_eight_options_and_long_help_lists_all() { .filter(|l| l.starts_with('-') && !l.starts_with("-h,") && !l.starts_with("-V,")) .count() }; - assert!(count(&short) <= 9, "{name} -h lists {} options:\n{short}", count(&short)); - assert!(count(&long) > count(&short), "{name} --help must list more than -h"); - assert!(short.contains("--json") && short.contains("--cwd"), "{name}"); + assert!( + count(&short) <= 9, + "{name} -h lists {} options:\n{short}", + count(&short) + ); + assert!( + count(&long) > count(&short), + "{name} --help must list more than -h" + ); + assert!( + short.contains("--json") && short.contains("--cwd"), + "{name}" + ); } let scan = cmd.find_subcommand_mut("scan").expect("scan"); let long = scan.render_long_help().to_string(); - assert!(!long.contains("--apply") && !long.contains("--vendor "), "{long}"); + assert!( + !long.contains("--apply") && !long.contains("--vendor "), + "{long}" + ); } diff --git a/crates/socket-patch-cli/tests/hosted_memory_engine.rs b/crates/socket-patch-cli/tests/hosted_memory_engine.rs index a340abb23..54826f34d 100644 --- a/crates/socket-patch-cli/tests/hosted_memory_engine.rs +++ b/crates/socket-patch-cli/tests/hosted_memory_engine.rs @@ -984,7 +984,8 @@ async fn a_vlt_project_is_withheld_as_offline() { .and_then(|w| w["detail"].as_str()) .expect("the preflight warning is reported"); assert!( - detail.contains("/patch/npm//") && detail.contains(": offline; nothing was written"), + detail.contains("/patch/npm//") + && detail.contains(": offline; nothing was written"), "the offline refusal quotes the redacted URL" ); assert!(output.changed_files.is_empty()); diff --git a/crates/socket-patch-cli/tests/hosted_memory_parity.rs b/crates/socket-patch-cli/tests/hosted_memory_parity.rs index b297eaf79..0af392eb4 100644 --- a/crates/socket-patch-cli/tests/hosted_memory_parity.rs +++ b/crates/socket-patch-cli/tests/hosted_memory_parity.rs @@ -754,7 +754,11 @@ fn policy_repo(socket_yml: &str) -> (Vec, BTreeMap>) { let mut patches = patches_from_overrides(&npm.join("overrides.json"), None); patches.extend(patches_from_overrides(&cargo.join("overrides.json"), None)); let mut repo: BTreeMap> = BTreeMap::new(); - for (root, dir) in [("apps/web", &npm), ("apps/legacy", &npm), ("services/api", &cargo)] { + for (root, dir) in [ + ("apps/web", &npm), + ("apps/legacy", &npm), + ("services/api", &cargo), + ] { for (rel, bytes) in fixture_files(&dir.join("input")) { repo.insert(format!("{root}/{rel}"), bytes); } @@ -773,7 +777,9 @@ fn two_phase( socket_patch_cli::hosted_memory::PathSelection, socket_patch_cli::hosted_memory::HostedScanInput, ) { - use socket_patch_cli::hosted_memory::{select_paths, PolicyFileInput, SelectOptions, TreeEntryInput}; + use socket_patch_cli::hosted_memory::{ + select_paths, PolicyFileInput, SelectOptions, TreeEntryInput, + }; let entries: Vec = files .iter() .map(|(p, bytes)| TreeEntryInput { @@ -814,15 +820,23 @@ fn two_phase( (selection, input) } -fn policy_input(files: &BTreeMap>) -> socket_patch_cli::hosted_memory::HostedScanInput { +fn policy_input( + files: &BTreeMap>, +) -> socket_patch_cli::hosted_memory::HostedScanInput { let (selection, input) = two_phase(files, options(false)); - assert!(selection.policy_error.is_none(), "{:?}", selection.policy_error); + assert!( + selection.policy_error.is_none(), + "{:?}", + selection.policy_error + ); input } /// Session options as selection of `files` would hand them over, without /// going through selection (for inputs a host may get wrong). -fn policy_options(files: &BTreeMap>) -> socket_patch_cli::hosted_memory::HostedScanOptions { +fn policy_options( + files: &BTreeMap>, +) -> socket_patch_cli::hosted_memory::HostedScanOptions { let (selection, _) = two_phase(files, options(false)); let mut opts = options(false); opts.policy_paths = Some(selection.policy_paths); @@ -854,25 +868,44 @@ async fn parity_socket_yml_filters_the_same_roots_and_packages() { let server = MockServer::start().await; mount_api(&server, &patches).await; let (selection, input) = two_phase(&repo, options(false)); - assert!(selection.policy_error.is_none(), "{:?}", selection.policy_error); + assert!( + selection.policy_error.is_none(), + "{:?}", + selection.policy_error + ); let memory = run_engine(&server, input).await; assert!(memory.policy_error.is_none(), "{:?}", memory.policy_error); let roots: Vec<&str> = memory.projects.iter().map(|p| p.root.as_str()).collect(); - assert_eq!(roots, vec!["apps/web", "services/api"], "the ignored root is not processed"); + assert_eq!( + roots, + vec!["apps/web", "services/api"], + "the ignored root is not processed" + ); // Selection reports the root it excluded; nothing of it is streamed. assert!(selection .ignored_sample .iter() .any(|i| i.path == "apps/legacy/package-lock.json" && i.reason == "policy_path_excluded")); - assert!(!selection.fetch_text.iter().chain(&selection.present_only).any(|p| p.starts_with("apps/legacy/"))); + assert!(!selection + .fetch_text + .iter() + .chain(&selection.present_only) + .any(|p| p.starts_with("apps/legacy/"))); let memory_policy = memory.policy.clone().expect("policy block"); assert_eq!(memory_policy["source"], "file"); let mut disk_filtered = std::collections::BTreeSet::new(); for root in ["apps/web", "apps/legacy", "services/api"] { let disk = run_disk_in(&server, &repo, root, false); - assert_eq!(disk.envelope["status"], "success", "{root}: {}", disk.stderr); - assert_eq!(disk.envelope["policy"]["sha256"], memory_policy["sha256"], "{root}"); + assert_eq!( + disk.envelope["status"], "success", + "{root}: {}", + disk.stderr + ); + assert_eq!( + disk.envelope["policy"]["sha256"], memory_policy["sha256"], + "{root}" + ); disk_filtered.extend(filtered_set(&disk.envelope["policy"])); if let Some(project) = memory.projects.iter().find(|p| p.root == root) { assert_eq!(project.redirect, disk.envelope["redirect"], "{root}"); @@ -883,13 +916,24 @@ async fn parity_socket_yml_filters_the_same_roots_and_packages() { .collect(); assert_eq!(memory_changed, disk.changed, "{root}"); } else { - assert!(disk.changed.is_empty(), "{root}: an ignored root changes nothing"); + assert!( + disk.changed.is_empty(), + "{root}: an ignored root changes nothing" + ); } } let mut memory_filtered = filtered_set(&memory_policy); - memory_filtered.insert(("apps/legacy".to_string(), None, "policy_path_excluded".to_string())); + memory_filtered.insert(( + "apps/legacy".to_string(), + None, + "policy_path_excluded".to_string(), + )); assert_eq!(memory_filtered, disk_filtered); - assert!(disk_filtered.contains(&("apps/legacy".to_string(), None, "policy_path_excluded".to_string()))); + assert!(disk_filtered.contains(&( + "apps/legacy".to_string(), + None, + "policy_path_excluded".to_string() + ))); assert!(disk_filtered.contains(&( "services/api".to_string(), Some("pkg:cargo/serde@1.0.190".to_string()), @@ -903,9 +947,17 @@ async fn parity_socket_yml_severity_floor() { let server = MockServer::start().await; mount_api(&server, &patches).await; let memory = run_engine(&server, policy_input(&repo)).await; - let web = memory.projects.iter().find(|p| p.root == "apps/web").unwrap(); + let web = memory + .projects + .iter() + .find(|p| p.root == "apps/web") + .unwrap(); assert!(web.redirected.is_empty(), "{:#}", web.redirect); - assert!(web.skipped.iter().any(|s| s.reason == "policy_severity"), "{:?}", web.skipped); + assert!( + web.skipped.iter().any(|s| s.reason == "policy_severity"), + "{:?}", + web.skipped + ); assert!(engine_changed(&memory).is_empty()); let disk = run_disk_in(&server, &repo, "apps/web", false); assert!(disk.changed.is_empty()); @@ -931,8 +983,15 @@ async fn memory_policy_file_withheld_or_invalid_is_a_policy_error() { assert_eq!(err.code, "socket_yml_invalid"); assert!(out.projects.is_empty() && out.changed_files.is_empty() && out.policy.is_none()); // Streamed present-without-content. - let out = run_engine(&server, build_input(&withheld, &["socket.yml"], opts.clone())).await; - assert_eq!(out.policy_error.expect("policyError").code, "socket_yml_invalid"); + let out = run_engine( + &server, + build_input(&withheld, &["socket.yml"], opts.clone()), + ) + .await; + assert_eq!( + out.policy_error.expect("policyError").code, + "socket_yml_invalid" + ); // Content other than what selection read. let mut changed = repo.clone(); changed.insert("socket.yml".to_string(), b"version: 2\n".to_vec()); @@ -943,7 +1002,10 @@ async fn memory_policy_file_withheld_or_invalid_is_a_policy_error() { let mut no_sha = opts.clone(); no_sha.policy_sha256 = None; let out = run_engine(&server, build_input(&repo, &[], no_sha)).await; - assert_eq!(out.policy_error.expect("policyError").code, "socket_yml_invalid"); + assert_eq!( + out.policy_error.expect("policyError").code, + "socket_yml_invalid" + ); // Invalid content: selection refuses it before anything is fetched. let (_, bad) = policy_repo("version: 2\npatches:\n apiUrl: https://evil.example\n"); let (selection, _) = two_phase(&bad, options(false)); @@ -958,7 +1020,10 @@ async fn memory_policy_file_withheld_or_invalid_is_a_policy_error() { let mut half = opts.clone(); half.no_socket_yml = Some(true); let out = run_engine(&server, build_input(&repo, &[], half)).await; - assert_eq!(out.policy_error.expect("policyError").code, "socket_yml_invalid"); + assert_eq!( + out.policy_error.expect("policyError").code, + "socket_yml_invalid" + ); // noSocketYml skips it on both sides. let mut bypass = options(false); bypass.no_socket_yml = Some(true); @@ -979,7 +1044,10 @@ async fn memory_min_severity_option_beats_the_file() { let (_, input) = two_phase(&repo, opts); let out = run_engine(&server, input).await; let policy = out.policy.unwrap(); - assert_eq!(policy["minSeverity"], serde_json::json!({"value": null, "source": "flag"})); + assert_eq!( + policy["minSeverity"], + serde_json::json!({"value": null, "source": "flag"}) + ); assert!(out.projects.iter().any(|p| !p.redirected.is_empty())); let mut bad = options(false); bad.min_severity = Some("severe".to_string()); @@ -988,7 +1056,9 @@ async fn memory_min_severity_option_beats_the_file() { #[test] fn selection_applies_the_path_policy_and_fails_closed() { - use socket_patch_cli::hosted_memory::{select_paths, PolicyFileInput, SelectOptions, TreeEntryInput}; + use socket_patch_cli::hosted_memory::{ + select_paths, PolicyFileInput, SelectOptions, TreeEntryInput, + }; let blob = |path: &str, mode: &str| TreeEntryInput { path: path.to_string(), mode: mode.to_string(), @@ -1014,19 +1084,34 @@ fn selection_applies_the_path_policy_and_fails_closed() { }; let yml = "version: 2\npatches:\n ignorePaths: [\"/apps/old/\"]\n"; let selection = select_paths(&entries, &with(vec![text("socket.yml", yml)])); - assert!(selection.policy_error.is_none(), "{:?}", selection.policy_error); + assert!( + selection.policy_error.is_none(), + "{:?}", + selection.policy_error + ); assert_eq!(selection.policy_paths, vec!["socket.yml"]); assert_eq!(selection.policy_sha256.as_ref().map(String::len), Some(64)); assert!(selection.fetch_text.contains(&"socket.yml".to_string())); assert_eq!(selection.roots, vec!["apps/web"]); // Excluded roots (file list and built-in ignores, any case) are // reported and never streamed. - for path in ["apps/old/yarn.lock", "apps/web/tests/app/package-lock.json", "Fixtures/x/yarn.lock"] { + for path in [ + "apps/old/yarn.lock", + "apps/web/tests/app/package-lock.json", + "Fixtures/x/yarn.lock", + ] { assert!( - selection.ignored_sample.iter().any(|i| i.path == path && i.reason == "policy_path_excluded"), + selection + .ignored_sample + .iter() + .any(|i| i.path == path && i.reason == "policy_path_excluded"), "{path}: {selection:?}" ); - assert!(!selection.fetch_text.contains(&path.to_string()) && !selection.present_only.contains(&path.to_string()), "{path}"); + assert!( + !selection.fetch_text.contains(&path.to_string()) + && !selection.present_only.contains(&path.to_string()), + "{path}" + ); } // Named roots are explicit: the built-in ignores do not apply. let named = select_paths( @@ -1044,9 +1129,16 @@ fn selection_applies_the_path_policy_and_fails_closed() { text: None, missing: Some(true), }; - for files in [vec![], vec![missing], vec![text("socket.yml", "version: 2\npatches:\n apiUrl: x\n")]] { + for files in [ + vec![], + vec![missing], + vec![text("socket.yml", "version: 2\npatches:\n apiUrl: x\n")], + ] { let out = select_paths(&entries, &with(files)); - assert_eq!(out.policy_error.as_ref().map(|e| e.code.as_str()), Some("socket_yml_invalid")); + assert_eq!( + out.policy_error.as_ref().map(|e| e.code.as_str()), + Some("socket_yml_invalid") + ); assert!(out.roots.is_empty() && out.fetch_text.is_empty(), "{out:?}"); assert_eq!(out.policy_paths, vec!["socket.yml"]); } @@ -1054,8 +1146,14 @@ fn selection_applies_the_path_policy_and_fails_closed() { assert!(out.policy_error.is_some()); // A symlinked policy file is never read. entries.push(blob("socket.yaml", "120000")); - let out = select_paths(&entries, &with(vec![text("socket.yml", yml), text("socket.yaml", yml)])); - assert_eq!(out.policy_error.map(|e| e.code), Some("socket_yml_invalid".to_string())); + let out = select_paths( + &entries, + &with(vec![text("socket.yml", yml), text("socket.yaml", yml)]), + ); + assert_eq!( + out.policy_error.map(|e| e.code), + Some("socket_yml_invalid".to_string()) + ); // noSocketYml: only the built-in ignores; the file need not be passed. let out = select_paths( &entries, @@ -1086,8 +1184,13 @@ async fn memory_negation_reincludes_a_default_ignored_root() { ); let (selection, input) = two_phase(&repo, options(false)); assert_eq!(selection.roots, vec!["e2e/tests"]); - assert!(selection.fetch_text.contains(&"e2e/tests/package-lock.json".to_string())); - assert!(!selection.fetch_text.iter().any(|p| p.starts_with("x/")), "{selection:?}"); + assert!(selection + .fetch_text + .contains(&"e2e/tests/package-lock.json".to_string())); + assert!( + !selection.fetch_text.iter().any(|p| p.starts_with("x/")), + "{selection:?}" + ); assert!(selection .ignored_sample .iter() @@ -1095,19 +1198,31 @@ async fn memory_negation_reincludes_a_default_ignored_root() { let memory = run_engine(&server, input).await; let roots: Vec<&str> = memory.projects.iter().map(|p| p.root.as_str()).collect(); assert_eq!(roots, vec!["e2e/tests"]); - assert!(!memory.projects[0].redirected.is_empty(), "{:#}", memory.projects[0].redirect); + assert!( + !memory.projects[0].redirected.is_empty(), + "{:#}", + memory.projects[0].redirect + ); // Given every root anyway, the session applies the same filter itself. let direct = run_engine(&server, build_input(&repo, &[], policy_options(&repo))).await; let roots: Vec<&str> = direct.projects.iter().map(|p| p.root.as_str()).collect(); assert_eq!(roots, vec!["e2e/tests"]); let entry = &direct.policy.as_ref().unwrap()["filtered"][0]; - assert_eq!((entry["project"].as_str(), entry["detail"].as_str()), (Some("x/tests"), Some("tests/ (built-in default)"))); + assert_eq!( + (entry["project"].as_str(), entry["detail"].as_str()), + (Some("x/tests"), Some("tests/ (built-in default)")) + ); // Disk patches the same root the same way. let disk = run_disk_in(&server, &repo, "e2e/tests", false); assert_eq!(disk.envelope["status"], "success", "{}", disk.stderr); assert_eq!(memory.projects[0].redirect, disk.envelope["redirect"]); let memory_changed = engine_changed(&memory); - assert_eq!(memory_changed, disk.changed, "{}", describe(&memory_changed)); + assert_eq!( + memory_changed, + disk.changed, + "{}", + describe(&memory_changed) + ); } diff --git a/crates/socket-patch-cli/tests/hosted_memory_rollout.rs b/crates/socket-patch-cli/tests/hosted_memory_rollout.rs index ccaf92cc4..3c104abf4 100644 --- a/crates/socket-patch-cli/tests/hosted_memory_rollout.rs +++ b/crates/socket-patch-cli/tests/hosted_memory_rollout.rs @@ -237,7 +237,9 @@ async fn memory_selected( files: &BTreeMap>, mut o: HostedScanOptions, ) -> HostedScanOutput { - use socket_patch_cli::hosted_memory::{select_paths, PolicyFileInput, SelectOptions, TreeEntryInput}; + use socket_patch_cli::hosted_memory::{ + select_paths, PolicyFileInput, SelectOptions, TreeEntryInput, + }; let entries: Vec = files .iter() .map(|(p, bytes)| TreeEntryInput { @@ -265,7 +267,11 @@ async fn memory_selected( ..SelectOptions::default() }, ); - assert!(selection.policy_error.is_none(), "{:?}", selection.policy_error); + assert!( + selection.policy_error.is_none(), + "{:?}", + selection.policy_error + ); let fetched: BTreeMap> = selection .fetch_text .iter() @@ -424,7 +430,11 @@ async fn socket_yml_policy_and_cap_converge_on_disk_and_in_memory() { b"version: 2\npatches:\n includePaths: [\"/apps/\"]\n minSeverity: high\n maxNewPatches: 2\n" .to_vec(), ); - lock(&mut files, "apps/one", &["mem-a", "mem-b", "mem-c", "mem-d", "mem-e"]); + lock( + &mut files, + "apps/one", + &["mem-a", "mem-b", "mem-c", "mem-d", "mem-e"], + ); lock(&mut files, "apps/two", &["mem-b", "mem-c", "mem-d"]); lock(&mut files, "legacy", &["mem-b", "mem-e"]); let dirs = ["apps/one", "apps/two", "legacy"]; @@ -435,8 +445,16 @@ async fn socket_yml_policy_and_cap_converge_on_disk_and_in_memory() { }; let expected: [Vec>; 3] = [ vec![vec!["mem-e", "mem-b"], vec!["mem-b"], vec![]], - vec![vec!["mem-e", "mem-b", "mem-c"], vec!["mem-b", "mem-c"], vec![]], - vec![vec!["mem-e", "mem-b", "mem-c"], vec!["mem-b", "mem-c"], vec![]], + vec![ + vec!["mem-e", "mem-b", "mem-c"], + vec!["mem-b", "mem-c"], + vec![], + ], + vec![ + vec!["mem-e", "mem-b", "mem-c"], + vec!["mem-b", "mem-c"], + vec![], + ], ]; let mut mem_files = files.clone(); @@ -449,7 +467,10 @@ async fn socket_yml_policy_and_cap_converge_on_disk_and_in_memory() { "run {}", run + 1 ); - assert_eq!(mem.policy.as_ref().map(|p| p["source"].clone()), Some(json!("file"))); + assert_eq!( + mem.policy.as_ref().map(|p| p["source"].clone()), + Some(json!("file")) + ); mem_files = apply(&mem_files, &mem); assert_eq!(&pins(&mem_files), want, "memory run {}", run + 1); @@ -469,7 +490,14 @@ async fn socket_yml_policy_and_cap_converge_on_disk_and_in_memory() { let (code, stdout, changed) = run_disk_args( &server, &disk_files, - &["--no-socket-yml", "--max-new-patches", "1", "apps/one", "apps/two", "legacy"], + &[ + "--no-socket-yml", + "--max-new-patches", + "1", + "apps/one", + "apps/two", + "legacy", + ], ); assert_eq!(code, 0, "{stdout}"); disk_files.extend(changed); diff --git a/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs b/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs index db2aab79f..6ce32e653 100644 --- a/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs +++ b/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs @@ -519,7 +519,11 @@ fn maven_hosted_get_state_attests_without_manifest( &[(purl, vlt_hosted_common::legacy_record_from_view(&view))], ); let out = run_vex(&binary(), project, &offline); - assert_eq!(out.code, Some(0), "a pre-v5 ledger record serves offline: {out}"); + assert_eq!( + out.code, + Some(0), + "a pre-v5 ledger record serves offline: {out}" + ); assert_attested(out.doc(), purl, uuid, Marker::Redirected, &vulns); quiet.assert_no_requests(); @@ -800,7 +804,11 @@ fn nuget_hosted_manifestless_vex(root: &Path, uuid: &str, purl: &str) { &[(purl, vlt_hosted_common::legacy_record_from_view(&view))], ); let out = run(VexRun::offline()); - assert_eq!(out.code, Some(0), "a pre-v5 ledger record serves offline: {out}"); + assert_eq!( + out.code, + Some(0), + "a pre-v5 ledger record serves offline: {out}" + ); assert_attested(out.doc(), purl, uuid, Marker::Redirected, vulns); std::fs::write( diff --git a/crates/socket-patch-cli/tests/in_process_redirect.rs b/crates/socket-patch-cli/tests/in_process_redirect.rs index 066983c7a..a3e2a6c77 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect.rs @@ -851,9 +851,10 @@ async fn scan_redirect_rewrites_crlf_and_bom_yarn_berry_locks_and_rollback_resto "{label}: rollback restores the pristine CRLF lock (upstream checksum \ re-derived from the registry tarball)" ); - let pkg: serde_json::Value = - serde_json::from_str(&std::fs::read_to_string(tmp.path().join("package.json")).unwrap()) - .unwrap(); + let pkg: serde_json::Value = serde_json::from_str( + &std::fs::read_to_string(tmp.path().join("package.json")).unwrap(), + ) + .unwrap(); assert!( pkg.get("resolutions").is_none(), "{label}: rollback drops the resolutions pin: {pkg}" diff --git a/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs b/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs index 61ec4bd3f..a78d10282 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs @@ -308,11 +308,15 @@ async fn scan_redirect_vlt_artifact_fetch_error() { let detail = warning_detail(&doc, UNVERIFIABLE); let redacted = url.replace(&format!("/{TOKEN}/"), "//"); assert!( - detail.starts_with(&format!("vlt would fail to verify {redacted}: fetch error ")) - && detail.ends_with(&format!("; nothing was written for {PURL}")), + detail.starts_with(&format!( + "vlt would fail to verify {redacted}: fetch error " + )) && detail.ends_with(&format!("; nothing was written for {PURL}")), "the fetch-error refusal quotes the redacted URL" ); - assert!(!detail.contains(TOKEN), "the grant token never reaches the warning"); + assert!( + !detail.contains(TOKEN), + "the grant token never reaches the warning" + ); } async fn redirect_chain(hops: usize) -> (Value, tempfile::TempDir) { diff --git a/crates/socket-patch-cli/tests/in_process_redirect_pdm.rs b/crates/socket-patch-cli/tests/in_process_redirect_pdm.rs index c7adfe131..f74c5c90c 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect_pdm.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect_pdm.rs @@ -187,7 +187,9 @@ async fn mock_api(server: &MockServer) { .mount(server) .await; Mock::given(method("GET")) - .and(path_regex(format!("^/v0/orgs/{ORG}/patches/by-package/.+$"))) + .and(path_regex(format!( + "^/v0/orgs/{ORG}/patches/by-package/.+$" + ))) .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ "patches": [{ "uuid": UUID, "purl": RECORD_PURL, @@ -368,9 +370,12 @@ fn legacy_record(view: &serde_json::Value) -> serde_json::Value { .remove("publishedAt") .unwrap_or_else(|| serde_json::json!("2024-01-01T00:00:00Z")); obj.insert("exportedAt".to_string(), exported); - obj.entry("description").or_insert_with(|| serde_json::json!("x")); - obj.entry("license").or_insert_with(|| serde_json::json!("MIT")); - obj.entry("tier").or_insert_with(|| serde_json::json!("free")); + obj.entry("description") + .or_insert_with(|| serde_json::json!("x")); + obj.entry("license") + .or_insert_with(|| serde_json::json!("MIT")); + obj.entry("tier") + .or_insert_with(|| serde_json::json!("free")); record } @@ -441,7 +446,11 @@ async fn lock_only_pdm_project_redirects_attests_rescans_and_rolls_back() { // 2. Idempotent re-scan: no further edits, lock byte-identical. let code = run(hosted_args(tmp.path(), server.uri(), None)).await; assert_eq!(code, 0); - assert_eq!(read(&lock_path), redirected, "re-scan must not touch the lock"); + assert_eq!( + read(&lock_path), + redirected, + "re-scan must not touch the lock" + ); // 3. The committed state, manifest-less, attests (and only while wired). assert_manifestless_vex(tmp.path(), LOCK); @@ -494,12 +503,19 @@ async fn hatchling_build_backend_does_not_veto_the_pdm_lock_redirect() { .iter() .filter(|r| r.url.path().ends_with(&format!("/patches/view/{UUID}"))) .count(); - assert_eq!(views, 1, "the pdm redirect must be confirmed despite the hatch backend"); + assert_eq!( + views, 1, + "the pdm redirect must be confirmed despite the hatch backend" + ); assert_manifestless_vex(tmp.path(), LOCK); let code = rollback_hosted(tmp.path(), &server).await; assert_eq!(code, 0, "rollback must succeed"); - assert_eq!(read(&lock_path), LOCK, "rollback must restore the pristine lock"); + assert_eq!( + read(&lock_path), + LOCK, + "rollback must restore the pristine lock" + ); } /// The legacy `[metadata.files]` lock (lock_version 2) redirects the package diff --git a/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs b/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs index 3c33af6d0..e8f743ccb 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs @@ -60,7 +60,8 @@ const MAJOR_ENV: &str = socket_patch_core::utils::pipenv::MAJOR_OVERRIDE_ENV; const LOCK: &str = include_str!("../../socket-patch-core/tests/fixtures/pipenv/2026.8.0/Pipfile.lock"); -const PIPFILE: &str = include_str!("../../socket-patch-core/tests/fixtures/pipenv/2026.8.0/Pipfile"); +const PIPFILE: &str = + include_str!("../../socket-patch-core/tests/fixtures/pipenv/2026.8.0/Pipfile"); /// The upstream and patched bytes of the record's one file, so the venv /// tests can materialize a real `Ready` (upstream) install. @@ -125,7 +126,9 @@ async fn mock_api(server: &MockServer) { .mount(server) .await; Mock::given(method("GET")) - .and(path_regex(format!("^/v0/orgs/{ORG}/patches/by-package/.+$"))) + .and(path_regex(format!( + "^/v0/orgs/{ORG}/patches/by-package/.+$" + ))) .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ "patches": [{ "uuid": UUID, "purl": RECORD_PURL, @@ -374,8 +377,15 @@ async fn lock_only_pipenv_project_redirects_attests_rescans_and_rolls_back() { ); let before: serde_json::Value = serde_json::from_str(LOCK).unwrap(); let after: serde_json::Value = serde_json::from_str(&redirected).unwrap(); - assert_eq!(after["_meta"], before["_meta"], "the Pipfile content hash stays"); - assert_eq!(read(&tmp.path().join("Pipfile")), PIPFILE, "Pipfile untouched"); + assert_eq!( + after["_meta"], before["_meta"], + "the Pipfile content hash stays" + ); + assert_eq!( + read(&tmp.path().join("Pipfile")), + PIPFILE, + "Pipfile untouched" + ); assert_no_ledger(tmp.path()); // Attested from this run's fetched record (keyed by RECORD_PURL, assume // applied) although the base purl the run confirmed differs from the @@ -383,13 +393,25 @@ async fn lock_only_pipenv_project_redirects_attests_rescans_and_rolls_back() { let vex: serde_json::Value = serde_json::from_str(&read(&vex_path)).unwrap(); let statements = vex["statements"].as_array().expect("statements"); assert_eq!(statements.len(), 1, "{vex}"); - assert_eq!(statements[0]["vulnerability"]["name"].as_str(), Some(GHSA), "{vex}"); - assert_eq!(statements[0]["status"].as_str(), Some("not_affected"), "{vex}"); + assert_eq!( + statements[0]["vulnerability"]["name"].as_str(), + Some(GHSA), + "{vex}" + ); + assert_eq!( + statements[0]["status"].as_str(), + Some("not_affected"), + "{vex}" + ); // 2. Idempotent re-scan: no further edits, lock byte-identical. let code = run(hosted_args(tmp.path(), server.uri(), None)).await; assert_eq!(code, 0); - assert_eq!(read(&lock_path), redirected, "re-scan must not touch the lock"); + assert_eq!( + read(&lock_path), + redirected, + "re-scan must not touch the lock" + ); assert_no_ledger(tmp.path()); // Manifest-less VEX over the committed state (the depscan / CI shape). @@ -399,7 +421,11 @@ async fn lock_only_pipenv_project_redirects_attests_rescans_and_rolls_back() { // 3. rollback restores the upstream registry entry. roll_back(tmp.path(), &server).await; - assert_eq!(read(&lock_path), LOCK, "rollback must restore the pristine lock byte for byte"); + assert_eq!( + read(&lock_path), + LOCK, + "rollback must restore the pristine lock byte for byte" + ); } #[tokio::test] @@ -422,7 +448,10 @@ async fn legacy_installer_major_selects_path_references() { "Pipenv 7–11 install `path` references: {redirected}" ); assert!(entry.get("file").is_none(), "{entry}"); - assert_eq!(entry["hashes"], serde_json::json!([format!("sha256:{}", sha256())])); + assert_eq!( + entry["hashes"], + serde_json::json!([format!("sha256:{}", sha256())]) + ); // The legacy `path` reference is discovered just like `file`. manifestless_vex(tmp.path(), "pipenv legacy path", &|p: &Path| { @@ -443,7 +472,9 @@ async fn stale_pipfile_lock_does_not_veto_the_requirements_redirect() { write_project(tmp.path()); // The Pipfile.lock left behind pins a DIFFERENT package; the project // installs from requirements.txt. - let stale = LOCK.replace("\"urllib3\"", "\"six\"").replace("==1.26.18", "==1.16.0"); + let stale = LOCK + .replace("\"urllib3\"", "\"six\"") + .replace("==1.26.18", "==1.16.0"); std::fs::write(tmp.path().join("Pipfile.lock"), &stale).unwrap(); // An unpatched, unhashed sibling makes the file's hash mode derivable, // so rollback can restore the hosted line (a file whose every line is a @@ -471,10 +502,7 @@ async fn stale_pipfile_lock_does_not_veto_the_requirements_redirect() { }); roll_back(tmp.path(), &server).await; - assert_eq!( - read(&tmp.path().join("requirements.txt")), - REQS - ); + assert_eq!(read(&tmp.path().join("requirements.txt")), REQS); assert_eq!(read(&tmp.path().join("Pipfile.lock")), stale); } @@ -559,16 +587,27 @@ async fn warm_venv_with_the_upstream_release_is_not_attested() { // attested and the embedded-VEX contract fails the command. let code = run(hosted_args(tmp.path(), server.uri(), Some(&vex_path))).await; let redirected = read(&lock_path); - assert!(redirected.contains(HOSTED_URL), "the lock is still repointed: {redirected}"); + assert!( + redirected.contains(HOSTED_URL), + "the lock is still repointed: {redirected}" + ); let attested = vex_path .exists() .then(|| serde_json::from_str::(&read(&vex_path)).unwrap()) .and_then(|v| v["statements"].as_array().map(Vec::len)) .unwrap_or(0); - assert_eq!(attested, 0, "a stale install must not be attested from the fetched record"); + assert_eq!( + attested, 0, + "a stale install must not be attested from the fetched record" + ); assert_ne!(code, 0, "nothing to attest fails the embedded-VEX run"); assert_eq!( - std::fs::read(site_packages(tmp.path()).join("urllib3").join("response.py")).unwrap(), + std::fs::read( + site_packages(tmp.path()) + .join("urllib3") + .join("response.py") + ) + .unwrap(), UPSTREAM, "the probe is read-only" ); diff --git a/crates/socket-patch-cli/tests/in_process_redirect_pnpm.rs b/crates/socket-patch-cli/tests/in_process_redirect_pnpm.rs index 4519d1d4d..a0393d106 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect_pnpm.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect_pnpm.rs @@ -56,7 +56,10 @@ async fn rollback_hosted(cwd: &Path, server: &MockServer) -> i32 { }))) .mount(server) .await; - std::env::set_var("SOCKET_NPM_REGISTRY", format!("{}/npm-registry", server.uri())); + std::env::set_var( + "SOCKET_NPM_REGISTRY", + format!("{}/npm-registry", server.uri()), + ); let code = rollback::run(RollbackArgs { targets: Vec::new(), common: socket_patch_cli::args::GlobalArgs { @@ -805,7 +808,11 @@ async fn hosted_pnpm_manifestless_vex_from_lockfile_legacy_ledger_and_api() { ..VexRun::offline() }, ); - assert_eq!(out.code, Some(0), "[{lock_name}] legacy ledger, offline: {out}"); + assert_eq!( + out.code, + Some(0), + "[{lock_name}] legacy ledger, offline: {out}" + ); assert_attested(out.doc(), PURL, UUID, Marker::Redirected, vulns); assert_eq!(api.request_count(), seen); diff --git a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs index d1b5b1607..605731fc5 100644 --- a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs +++ b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs @@ -1144,8 +1144,9 @@ async fn a_git_pattern_hosted_pin_is_refused_not_restored_to_the_registry() { assert!( envelope["hosted"]["failed"][0]["error"] .as_str() - .is_some_and(|e| e.contains("installs from git") - && e.contains("`git checkout -- yarn.lock`")), + .is_some_and( + |e| e.contains("installs from git") && e.contains("`git checkout -- yarn.lock`") + ), "{envelope}" ); assert_eq!( diff --git a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs b/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs index 8779d2e84..9c08880b2 100644 --- a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs +++ b/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs @@ -221,7 +221,10 @@ async fn vlt_repair_reports_a_missing_ledger() { lock_bytes, "{lock:?}" ); - assert!(tmp.path().join(rel()).join("index.js").is_file(), "{lock:?}"); + assert!( + tmp.path().join(rel()).join("index.js").is_file(), + "{lock:?}" + ); } } diff --git a/crates/socket-patch-cli/tests/rollback/rollback_duality_invariants.rs b/crates/socket-patch-cli/tests/rollback/rollback_duality_invariants.rs index d4830cbd9..31f457502 100644 --- a/crates/socket-patch-cli/tests/rollback/rollback_duality_invariants.rs +++ b/crates/socket-patch-cli/tests/rollback/rollback_duality_invariants.rs @@ -533,8 +533,7 @@ fn bare_word_target_stays_identifier_error() { )], false, ); - let manifest_before = - std::fs::read(socket.join("manifest.json")).expect("read manifest bytes"); + let manifest_before = std::fs::read(socket.join("manifest.json")).expect("read manifest bytes"); let (code, stdout, stderr) = run(tmp.path(), &["--offline", "lodash"]); assert_eq!( diff --git a/crates/socket-patch-cli/tests/scan/covgap_ecosystem_dispatch.rs b/crates/socket-patch-cli/tests/scan/covgap_ecosystem_dispatch.rs index 5fee90f88..87d4900a3 100644 --- a/crates/socket-patch-cli/tests/scan/covgap_ecosystem_dispatch.rs +++ b/crates/socket-patch-cli/tests/scan/covgap_ecosystem_dispatch.rs @@ -253,7 +253,10 @@ fn rollback_dispatch_branch_deno() { .unwrap_or_else(|e| panic!("rollback envelope must parse ({e}); stdout={stdout}")); let code = out.status.code().unwrap_or(-1); - assert_eq!(code, 0, "rollback --ecosystems=deno: expected exit 0; env={env}"); + assert_eq!( + code, 0, + "rollback --ecosystems=deno: expected exit 0; env={env}" + ); assert_eq!( env["status"], "success", "rollback --ecosystems=deno: expected success; env={env}" @@ -294,7 +297,8 @@ fn rollback_dispatch_branch_deno() { // The decisive check: the on-disk bytes are restored to ORIGINAL. let restored = std::fs::read(&verify_file).unwrap(); assert_eq!( - restored, ORIGINAL, + restored, + ORIGINAL, "rollback --ecosystems=deno: {} was not restored to its original bytes", verify_file.display() ); diff --git a/crates/socket-patch-cli/tests/scan/scan_invariants.rs b/crates/socket-patch-cli/tests/scan/scan_invariants.rs index c3316ee78..f4bb749e1 100644 --- a/crates/socket-patch-cli/tests/scan/scan_invariants.rs +++ b/crates/socket-patch-cli/tests/scan/scan_invariants.rs @@ -1787,7 +1787,11 @@ async fn report_only_scan_json_redirect_state_keys_on_lock_pins() { serde_json::json!([{ "purl": purl, "uuid": AGENT_WARN_UUID }]), "the lock pin is the record; envelope={v}" ); - assert_eq!(state["wiringLive"], serde_json::json!([purl]), "envelope={v}"); + assert_eq!( + state["wiringLive"], + serde_json::json!([purl]), + "envelope={v}" + ); // No pin, no ledger: the key must stay absent (additive contract). let clean = tempfile::tempdir().expect("tempdir"); @@ -1832,7 +1836,8 @@ async fn report_only_scan_json_ignores_a_stale_pre_v5_ledger_record() { integrity sha512-orig==\n", ) .unwrap(); - let ledger_before = std::fs::read(tmp.path().join(".socket/vendor/redirect-state.json")).unwrap(); + let ledger_before = + std::fs::read(tmp.path().join(".socket/vendor/redirect-state.json")).unwrap(); for extra in [&["--prune"][..], &["--mode", "agent", "--dry-run"][..]] { let (code, stdout, stderr) = run_scan(tmp.path(), &mock.uri(), extra); @@ -2053,10 +2058,7 @@ async fn scan_ignores_a_malformed_pre_v5_ledger() { "{extra:?}: a pre-v5 ledger is never read, so never reported: {stderr}" ); let v: serde_json::Value = serde_json::from_str(stdout.trim()).expect("valid JSON"); - assert!( - v.get("redirectState").is_none(), - "{extra:?}: envelope={v}" - ); + assert!(v.get("redirectState").is_none(), "{extra:?}: envelope={v}"); assert_eq!( std::fs::read(vendor_dir.join("redirect-state.json")).unwrap(), b"{ torn ledger", @@ -2090,7 +2092,11 @@ async fn ecosystems_filter_keeps_records_but_not_wiring_live() { /*with_record=*/ true, ); - let (code, stdout, stderr) = run_scan(tmp.path(), &mock.uri(), &["--mode", "agent", "--dry-run", "--ecosystems", "pypi"]); + let (code, stdout, stderr) = run_scan( + tmp.path(), + &mock.uri(), + &["--mode", "agent", "--dry-run", "--ecosystems", "pypi"], + ); assert_eq!(code, 0, "stdout={stdout}; stderr={stderr}"); let v: serde_json::Value = serde_json::from_str(stdout.trim()).expect("valid JSON"); let state = &v["redirectState"]; diff --git a/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs index 8ad94e551..64ea1197c 100644 --- a/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs +++ b/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs @@ -216,7 +216,11 @@ async fn paths_scope_narrows_the_query() { let tmp = tempfile::tempdir().unwrap(); write_two_subtree_project(tmp.path()); - let (code, stdout, stderr) = run_scan(tmp.path(), &server.uri(), &["packages/app", "--mode", "agent", "--dry-run"]); + let (code, stdout, stderr) = run_scan( + tmp.path(), + &server.uri(), + &["packages/app", "--mode", "agent", "--dry-run"], + ); assert_eq!( code, 0, "scoped scan must exit 0; stdout={stdout}; stderr={stderr}" @@ -477,7 +481,11 @@ async fn supplements_excluded_with_warning() { // purl reaches the API. let scoped_server = MockServer::start().await; mock_batch_empty(&scoped_server).await; - let (code, stdout, stderr) = run_scan(tmp.path(), &scoped_server.uri(), &["packages/app", "--mode", "agent", "--dry-run"]); + let (code, stdout, stderr) = run_scan( + tmp.path(), + &scoped_server.uri(), + &["packages/app", "--mode", "agent", "--dry-run"], + ); assert_eq!( code, 0, "scoped scan must exit 0; stdout={stdout}; stderr={stderr}" diff --git a/crates/socket-patch-cli/tests/update/covgap_commands_update.rs b/crates/socket-patch-cli/tests/update/covgap_commands_update.rs index b2d75aafc..16836e614 100644 --- a/crates/socket-patch-cli/tests/update/covgap_commands_update.rs +++ b/crates/socket-patch-cli/tests/update/covgap_commands_update.rs @@ -268,9 +268,8 @@ mod pty { let mut child = pair.slave.spawn_command(cmd).expect("spawn in PTY"); drop(pair.slave); - let reader_handle = crate::pty_io::PtyOutput::spawn( - pair.master.try_clone_reader().expect("clone reader"), - ); + let reader_handle = + crate::pty_io::PtyOutput::spawn(pair.master.try_clone_reader().expect("clone reader")); let mut killer = child.clone_killer(); std::thread::spawn(move || { @@ -338,7 +337,8 @@ mod pty { "a declined update exits 1 (codebase convention); got: {output}" ); assert!( - !output.contains("Updated socket-patch") && !output.contains("Reinstalled socket-patch"), + !output.contains("Updated socket-patch") + && !output.contains("Reinstalled socket-patch"), "a declined update must not report a swap; got: {output}" ); diff --git a/crates/socket-patch-cli/tests/yarn_berry_common/mod.rs b/crates/socket-patch-cli/tests/yarn_berry_common/mod.rs index e8ff74216..dffab3915 100644 --- a/crates/socket-patch-cli/tests/yarn_berry_common/mod.rs +++ b/crates/socket-patch-cli/tests/yarn_berry_common/mod.rs @@ -660,7 +660,9 @@ pub fn run_manifestless_vex_matrix(flow: &BerryVexFlow<'_>) -> Vec { crate::vex_e2e_common::assert_no_hosted_ledger(&fresh, "manifest-deleted"); } else { assert!( - fresh.join(socket_patch_core::vendor::VENDOR_STATE_REL).is_file(), + fresh + .join(socket_patch_core::vendor::VENDOR_STATE_REL) + .is_file(), "manifest-deleted: the vendored flow must have left its .socket/vendor ledger" ); } diff --git a/crates/socket-patch-core/src/api/ranking.rs b/crates/socket-patch-core/src/api/ranking.rs index 949931782..58ca27078 100644 --- a/crates/socket-patch-core/src/api/ranking.rs +++ b/crates/socket-patch-core/src/api/ranking.rs @@ -164,8 +164,14 @@ pub fn batch_supersedes(candidate: &BatchPatchInfo, applied: &BatchPatchInfo) -> /// classify a recorded patch (ALREADY vs UPGRADE) and to report /// `updates[]`, on the same records that pick the patch, so selection, /// classification and reporting cannot disagree. -pub fn search_result_supersedes(candidate: &PatchSearchResult, recorded: &PatchSearchResult) -> bool { - key_supersedes(&rank_search_result(candidate), &rank_search_result(recorded)) +pub fn search_result_supersedes( + candidate: &PatchSearchResult, + recorded: &PatchSearchResult, +) -> bool { + key_supersedes( + &rank_search_result(candidate), + &rank_search_result(recorded), + ) } fn key_supersedes(c: &RankKey<'_>, a: &RankKey<'_>) -> bool { @@ -371,12 +377,7 @@ mod tests { "2020-01-01T00:00:00Z", &["critical", "high"] ), - search_multi( - "z_new_low", - "free", - "2026-08-01T00:00:00Z", - &["low", "low"] - ), + search_multi("z_new_low", "free", "2026-08-01T00:00:00Z", &["low", "low"]), ]), "a_old_critical" ); diff --git a/crates/socket-patch-core/src/crawlers/python_crawler.rs b/crates/socket-patch-core/src/crawlers/python_crawler.rs index 7019e8945..ad4125e15 100644 --- a/crates/socket-patch-core/src/crawlers/python_crawler.rs +++ b/crates/socket-patch-core/src/crawlers/python_crawler.rs @@ -582,9 +582,7 @@ async fn pdm_saved_interpreter(cwd: &Path) -> Option { let saved = match read_regular_to_string(&cwd.join(".pdm-python")).await { Ok(text) => text.trim().to_string(), Err(_) => { - let text = read_regular_to_string(&cwd.join(".pdm.toml")) - .await - .ok()?; + let text = read_regular_to_string(&cwd.join(".pdm.toml")).await.ok()?; let doc = text.parse::().ok()?; doc.get("python")?.get("path")?.as_str()?.trim().to_string() } @@ -3670,7 +3668,11 @@ mod tests { fake_venv(&tmp.path().join("uv-env"), "venv"); let uv_env = env_of(&[( "UV_PROJECT_ENVIRONMENT", - tmp.path().join("uv-env").join("venv").to_string_lossy().into_owned(), + tmp.path() + .join("uv-env") + .join("venv") + .to_string_lossy() + .into_owned(), )]); assert_eq!( find_local_venv_site_packages_with(&project, &uv_env).await, diff --git a/crates/socket-patch-core/src/formats/cargo/mod.rs b/crates/socket-patch-core/src/formats/cargo/mod.rs index 58b4dc2bc..3e9cb9c5b 100644 --- a/crates/socket-patch-core/src/formats/cargo/mod.rs +++ b/crates/socket-patch-core/src/formats/cargo/mod.rs @@ -34,7 +34,6 @@ use crate::utils::purl::simple_purl; use crate::vendor::cargo_tag; use crate::vendor::lock_inventory::{LockIntegrity, LockfileEntry, SourceKind}; - // ── entry model ── /// The `[metadata]` key a v1 lock files `name`+`version`'s checksum under. @@ -332,7 +331,6 @@ pub(crate) fn parse_ref(spelled: &str) -> (&str, Option<&str>, Option<&str>) { (name, version, source) } - // ── the model ── /// One `Cargo.lock`, parsed once (see the module docs). @@ -500,7 +498,13 @@ impl CargoLock { uuid: &str, copy_tagged: bool, ) -> CopyClaim<'_> { - vendored_copy_claim(&self.packages, &self.unused, name, version, uuid, copy_tagged) + vendored_copy_claim( + &self.packages, + &self.unused, + name, + version, + uuid, + copy_tagged, + ) } } - diff --git a/crates/socket-patch-core/src/formats/composer/mod.rs b/crates/socket-patch-core/src/formats/composer/mod.rs index 8efa3c178..d45156ceb 100644 --- a/crates/socket-patch-core/src/formats/composer/mod.rs +++ b/crates/socket-patch-core/src/formats/composer/mod.rs @@ -22,7 +22,6 @@ use crate::utils::digest::sha1_hex; use crate::vendor::lock_inventory::{http_url, LockIntegrity, LockfileEntry, SourceKind}; use crate::vendor::path::{parse_vendor_path, VendorPathParts}; - // ── entry model ── /// One entry of a parsed `composer.lock` (see [`composer_lock_packages`]). @@ -107,7 +106,6 @@ pub(crate) fn composer_lock_packages(doc: &Value) -> Vec out } - // ── the model ── /// One `composer.lock`, read once (see the module docs). @@ -177,4 +175,3 @@ impl<'a> ComposerLock<'a> { out } } - diff --git a/crates/socket-patch-core/src/formats/gem/gemfile.rs b/crates/socket-patch-core/src/formats/gem/gemfile.rs index 8203c8a0a..34232745f 100644 --- a/crates/socket-patch-core/src/formats/gem/gemfile.rs +++ b/crates/socket-patch-core/src/formats/gem/gemfile.rs @@ -370,8 +370,14 @@ mod tests { #[test] fn escaped_quotes_and_hashes_inside_strings_stay_in_the_string() { - assert_eq!(key(", require: 'it\\'s', gitlab: \"x\""), Some("gitlab:".into())); - assert_eq!(key(", require: \"a\\\"b\", git: \"x\""), Some("git:".into())); + assert_eq!( + key(", require: 'it\\'s', gitlab: \"x\""), + Some("gitlab:".into()) + ); + assert_eq!( + key(", require: \"a\\\"b\", git: \"x\""), + Some("git:".into()) + ); assert_eq!(key(", local: \"#{name}\""), Some("local:".into())); } diff --git a/crates/socket-patch-core/src/formats/gem/hosted.rs b/crates/socket-patch-core/src/formats/gem/hosted.rs index 0416c3594..5dd4dc8b3 100644 --- a/crates/socket-patch-core/src/formats/gem/hosted.rs +++ b/crates/socket-patch-core/src/formats/gem/hosted.rs @@ -304,4 +304,3 @@ pub(crate) fn checksum_entry_span(lock: &str, name: &str, version: &str) -> Opti } None } - diff --git a/crates/socket-patch-core/src/formats/gem/mod.rs b/crates/socket-patch-core/src/formats/gem/mod.rs index 3a8f17af2..21bd39008 100644 --- a/crates/socket-patch-core/src/formats/gem/mod.rs +++ b/crates/socket-patch-core/src/formats/gem/mod.rs @@ -28,7 +28,6 @@ use crate::utils::digest::sha256_hex; use crate::utils::purl::simple_purl; use crate::vendor::lock_inventory::{http_url, LockIntegrity, LockfileEntry, SourceKind}; - /// The Bundler lockfiles, legacy spelling first: `Gemfile.lock` and /// `gems.locked` (what bundler writes instead when the manifest is /// `gems.rb`). @@ -223,7 +222,6 @@ impl<'t> GemfileLock<'t> { } } - /// Where a rubygems-compatible registry at `base` (no trailing `/`) serves /// `name`-`version`'s `.gem` — the inventory's resolved URL and ledger /// recovery's fetch URL. `None` for a non-http(s) base. diff --git a/crates/socket-patch-core/src/formats/mod.rs b/crates/socket-patch-core/src/formats/mod.rs index f3ea013a3..31ed9059e 100644 --- a/crates/socket-patch-core/src/formats/mod.rs +++ b/crates/socket-patch-core/src/formats/mod.rs @@ -26,13 +26,13 @@ //! [`registry()`] is the one table of which project files carry a lock or //! its wiring, and in which roles. +pub(crate) mod bun; pub mod cargo; pub mod composer; pub mod gem; pub(crate) mod maven; pub(crate) mod nuget; pub mod pnpm; -pub(crate) mod bun; pub mod registry; pub mod yarn; @@ -81,7 +81,11 @@ mod architecture_tests { .filter(|l| !l.trim_start().starts_with("//")) .collect::>() .join("\n"); - let used: Vec<&str> = IMPURE.iter().copied().filter(|n| code.contains(n)).collect(); + let used: Vec<&str> = IMPURE + .iter() + .copied() + .filter(|n| code.contains(n)) + .collect(); assert!( used.is_empty(), "{}: a format model uses {used:?} — models are pure (module docs)", diff --git a/crates/socket-patch-core/src/formats/pnpm/mod.rs b/crates/socket-patch-core/src/formats/pnpm/mod.rs index c7d47c1f2..a632c9c3a 100644 --- a/crates/socket-patch-core/src/formats/pnpm/mod.rs +++ b/crates/socket-patch-core/src/formats/pnpm/mod.rs @@ -39,7 +39,6 @@ use crate::utils::digest::is_sri_pin; use crate::vendor::lock_inventory::{http_url, LockIntegrity, LockfileEntry}; use crate::vendor::path::parse_vendor_path; - // ── entry model ── /// One `packages:` entry of a pnpm lock, read with the entry grammar @@ -283,7 +282,10 @@ fn lock_versions(text: &str) -> impl Iterator, u32)> + '_ { let value = rest.trim().trim_matches(|c| c == '\'' || c == '"'); let mut parts = value.split('.'); let major = parts.next().and_then(|m| m.parse::().ok()); - let minor = parts.next().and_then(|m| m.parse::().ok()).unwrap_or(0); + let minor = parts + .next() + .and_then(|m| m.parse::().ok()) + .unwrap_or(0); Some((major, minor)) }) } @@ -303,7 +305,8 @@ pub fn lock_version_major(text: &str) -> Option { /// rejects it): a `shrinkwrapVersion` lock (pnpm 1–2) or lockfileVersion /// 5.0–5.2 (pnpm 3–5). Later locks never get the `--store` note. pub fn may_need_store_flag(text: &str) -> bool { - text.lines().any(|line| line.starts_with("shrinkwrapVersion:")) + text.lines() + .any(|line| line.starts_with("shrinkwrapVersion:")) || lock_versions(text).any(|(major, minor)| major == Some(5) && minor <= 2) } @@ -491,7 +494,9 @@ pub(crate) fn vendored_npm_uuids(text: &str) -> HashSet { if !in_section { continue; } - if let Some(uuid) = lines::parse_key_line(line, 2).and_then(|(key, _, _)| vendored_npm_uuid(key)) { + if let Some(uuid) = + lines::parse_key_line(line, 2).and_then(|(key, _, _)| vendored_npm_uuid(key)) + { out.insert(uuid); } } @@ -508,17 +513,52 @@ mod tests { fn resolves_reads_every_key_generation_boundary_anchored() { let lock = |keys: &str| format!("lockfileVersion: '9.0'\n\npackages:\n\n{keys}"); let yes = [ - (" left-pad@1.3.0:\n resolution: {integrity: sha512-x}\n", "left-pad", "1.3.0"), - (" /left-pad@1.3.0:\n resolution: {}\n", "left-pad", "1.3.0"), - (" /left-pad/1.3.0:\n resolution: {}\n", "left-pad", "1.3.0"), - (" 'left-pad@1.3.0(react@18.0.0)':\n dev: false\n", "left-pad", "1.3.0"), - (" /left-pad/1.3.0_react@18.0.0:\n dev: false\n", "left-pad", "1.3.0"), - (" '@scope/name@1.0.0':\n dev: false\n", "@scope/name", "1.0.0"), - (" /@scope/name@1.0.0:\n dev: false\n", "@scope/name", "1.0.0"), - (" /@scope/name/1.0.0:\n dev: false\n", "@scope/name", "1.0.0"), + ( + " left-pad@1.3.0:\n resolution: {integrity: sha512-x}\n", + "left-pad", + "1.3.0", + ), + ( + " /left-pad@1.3.0:\n resolution: {}\n", + "left-pad", + "1.3.0", + ), + ( + " /left-pad/1.3.0:\n resolution: {}\n", + "left-pad", + "1.3.0", + ), + ( + " 'left-pad@1.3.0(react@18.0.0)':\n dev: false\n", + "left-pad", + "1.3.0", + ), + ( + " /left-pad/1.3.0_react@18.0.0:\n dev: false\n", + "left-pad", + "1.3.0", + ), + ( + " '@scope/name@1.0.0':\n dev: false\n", + "@scope/name", + "1.0.0", + ), + ( + " /@scope/name@1.0.0:\n dev: false\n", + "@scope/name", + "1.0.0", + ), + ( + " /@scope/name/1.0.0:\n dev: false\n", + "@scope/name", + "1.0.0", + ), ]; for (keys, name, version) in yes { - assert!(PnpmLock::parse(&lock(keys)).resolves(name, version), "{keys}"); + assert!( + PnpmLock::parse(&lock(keys)).resolves(name, version), + "{keys}" + ); } let no = [ (" left-pad@1.3.0-beta.1:\n dev: false\n", "left-pad", "1.3.0"), @@ -534,7 +574,10 @@ mod tests { ), ]; for (keys, name, version) in no { - assert!(!PnpmLock::parse(&lock(keys)).resolves(name, version), "{keys}"); + assert!( + !PnpmLock::parse(&lock(keys)).resolves(name, version), + "{keys}" + ); } // Keys outside `packages:` (importers, overrides) resolve nothing. let importers = "lockfileVersion: '9.0'\n\nimporters:\n\n left-pad@1.3.0:\n x: y\n"; @@ -557,7 +600,10 @@ mod tests { let other = "22222222-2222-4222-8222-222222222222"; assert!(!PnpmLock::parse(text).vendored_in_use(other)); let crlf = text.replace('\n', "\r\n"); - assert!(PnpmLock::parse(&crlf).vendored_in_use(UUID), "CRLF reads like LF"); + assert!( + PnpmLock::parse(&crlf).vendored_in_use(UUID), + "CRLF reads like LF" + ); } // An overrides declaration alone is not usage. let overrides = format!( diff --git a/crates/socket-patch-core/src/formats/registry.rs b/crates/socket-patch-core/src/formats/registry.rs index c690f6be8..de6adedb1 100644 --- a/crates/socket-patch-core/src/formats/registry.rs +++ b/crates/socket-patch-core/src/formats/registry.rs @@ -68,7 +68,11 @@ const fn row(path: &'static str, ecosystem: &'static str, roles: u8) -> FormatFi const REGISTRY: &[FormatFile] = &[ // ── npm family ── row("package-lock.json", "npm", HOSTED | VENDORED | PROBE | ROOT), - row("npm-shrinkwrap.json", "npm", HOSTED | VENDORED | PROBE | ROOT), + row( + "npm-shrinkwrap.json", + "npm", + HOSTED | VENDORED | PROBE | ROOT, + ), row( "pnpm-lock.yaml", "npm", @@ -119,7 +123,11 @@ const REGISTRY: &[FormatFile] = &[ row(".cargo/config", "cargo", HOSTED | VENDORED | PROBE), // ── composer ── row("composer.json", "composer", VENDORED), - row("composer.lock", "composer", HOSTED | VENDORED | PROBE | ROOT), + row( + "composer.lock", + "composer", + HOSTED | VENDORED | PROBE | ROOT, + ), // ── nuget ── row("nuget.config", "nuget", HOSTED | PROBE), row("NuGet.config", "nuget", HOSTED | PROBE), @@ -271,7 +279,11 @@ mod tests { paths.dedup(); assert_eq!(before, paths.len(), "duplicate registry path"); for f in REGISTRY.iter().filter(|f| f.has(ROOT)) { - assert!(!f.path.contains('/'), "{}: a root marker is a basename", f.path); + assert!( + !f.path.contains('/'), + "{}: a root marker is a basename", + f.path + ); } } diff --git a/crates/socket-patch-core/src/formats/yarn/mod.rs b/crates/socket-patch-core/src/formats/yarn/mod.rs index 389dadde0..3c7e71eec 100644 --- a/crates/socket-patch-core/src/formats/yarn/mod.rs +++ b/crates/socket-patch-core/src/formats/yarn/mod.rs @@ -64,7 +64,10 @@ mod tests { #[test] fn sniff_prefers_berry_and_skips_a_bom() { - assert_eq!(sniff_grammar("__metadata:\n version: 8\n"), Some(YarnLockGrammar::Berry)); + assert_eq!( + sniff_grammar("__metadata:\n version: 8\n"), + Some(YarnLockGrammar::Berry) + ); assert_eq!( sniff_grammar("\u{feff}# yarn lockfile v1\r\n"), Some(YarnLockGrammar::Classic) diff --git a/crates/socket-patch-core/src/hosted/guidance.rs b/crates/socket-patch-core/src/hosted/guidance.rs index 51ec85483..81bf03d76 100644 --- a/crates/socket-patch-core/src/hosted/guidance.rs +++ b/crates/socket-patch-core/src/hosted/guidance.rs @@ -173,9 +173,7 @@ pub fn pnpm_lock_carries_hosted_redirect( pub fn npm_lock_url_needles(artifact_url: &str) -> Vec { let mut needles: Vec = crate::patch::redirect::artifact_url_spellings(artifact_url).into(); - needles.push(crate::utils::uri::encode_uri_component( - artifact_url, - )); + needles.push(crate::utils::uri::encode_uri_component(artifact_url)); needles } @@ -310,11 +308,7 @@ fn npm_allow_remote_preamble(hosts: &[&str]) -> String { /// The auto-config variant: `allow-remote=all` was (or, on `--dry-run`, /// would be) written to the project `.npmrc`, so installs need no flags. -pub fn npm_allow_remote_configured_detail( - hosts: &[&str], - created: bool, - dry_run: bool, -) -> String { +pub fn npm_allow_remote_configured_detail(hosts: &[&str], created: bool, dry_run: bool) -> String { let how = match (created, dry_run) { (true, false) => "`allow-remote=all` was written to a new", (false, false) => "`allow-remote=all` was appended to the existing", diff --git a/crates/socket-patch-core/src/hosted/memory/discover.rs b/crates/socket-patch-core/src/hosted/memory/discover.rs index 6475a0cc0..9469b50ba 100644 --- a/crates/socket-patch-core/src/hosted/memory/discover.rs +++ b/crates/socket-patch-core/src/hosted/memory/discover.rs @@ -14,9 +14,7 @@ use std::time::Duration; use crate::api::client::{ApiError, ApiFuture, PatchApi}; use crate::api::ranking::cmp_search_results; -use crate::api::types::{ - BatchPackagePatches, PackageVendorResult, PatchResponse, SearchResponse, -}; +use crate::api::types::{BatchPackagePatches, PackageVendorResult, PatchResponse, SearchResponse}; use crate::utils::purl::{normalize_purl, strip_purl_qualifiers}; use super::types::MAX_REFERENCE_BATCH; diff --git a/crates/socket-patch-core/src/hosted/memory/limits.rs b/crates/socket-patch-core/src/hosted/memory/limits.rs index 9f895d6c9..da4dd695d 100644 --- a/crates/socket-patch-core/src/hosted/memory/limits.rs +++ b/crates/socket-patch-core/src/hosted/memory/limits.rs @@ -42,12 +42,7 @@ impl ResolvedOptions { /// as `flag`), then the socket.yml `patches.maxNewPatches`, then /// unlimited; `maxNewPatchesCap` only tightens it. pub(crate) fn max_new(&self, file: Option) -> crate::rollout::MaxNew { - crate::rollout::resolve_max_new( - self.max_new_patches, - None, - file, - self.max_new_patches_cap, - ) + crate::rollout::resolve_max_new(self.max_new_patches, None, file, self.max_new_patches_cap) } } @@ -79,8 +74,9 @@ pub(crate) fn resolve_options(options: &HostedScanOptions) -> Result None, Some(value) => Some(( - crate::policy::parse_min_severity(value) - .map_err(|e| EngineError::invalid("invalid_min_severity", format!("minSeverity: {e}")))?, + crate::policy::parse_min_severity(value).map_err(|e| { + EngineError::invalid("invalid_min_severity", format!("minSeverity: {e}")) + })?, crate::policy::OverrideSource::Flag, )), }; diff --git a/crates/socket-patch-core/src/hosted/memory/mod.rs b/crates/socket-patch-core/src/hosted/memory/mod.rs index 85a0d3c48..51651e5fc 100644 --- a/crates/socket-patch-core/src/hosted/memory/mod.rs +++ b/crates/socket-patch-core/src/hosted/memory/mod.rs @@ -58,17 +58,17 @@ pub use limits::SessionBuilder; pub use select::{candidate_files, safe_repo_path, select_paths}; pub use types::*; +use crate::policy::{ + canon, patch_severity_order, policy_block, FilterReason, FilteredEntry, MemoryPolicyFs, + PolicyError, PolicySource, Root, RootFile, SelectionPolicy, PATCHES_DISABLED, + POLICY_FILE_NAMES, +}; use crate::rollout::stage::{ classify, lookup_incomplete, mentioned_uuids, offers_from_results, Offers, RecordedIndex, Row, - Stage, - ROLLOUT_DEFERRED, + Stage, ROLLOUT_DEFERRED, }; use discover::Provider; use stages::{Planned, RewriteRefused, Rewritten, StageOptions}; -use crate::policy::{ - canon, patch_severity_order, policy_block, FilterReason, FilteredEntry, MemoryPolicyFs, PolicyError, - PolicySource, Root, RootFile, SelectionPolicy, PATCHES_DISABLED, POLICY_FILE_NAMES, -}; /// `"+"`; the sha comes from the /// `SOCKET_PATCH_GIT_SHA` build-time variable. @@ -419,11 +419,8 @@ fn memory_recorded( .map(|p| (purl.clone(), p.uuid.clone())) }) .collect(); - let merged = crate::ledgers::merge_ledger_records_for_updates( - manifest.as_ref(), - vendor.as_ref(), - &pins, - ); + let merged = + crate::ledgers::merge_ledger_records_for_updates(manifest.as_ref(), vendor.as_ref(), &pins); RecordedIndex::new(merged.as_deref(), &pins) } @@ -450,13 +447,20 @@ async fn engine( // The repo's socket.yml policy, before any root is processed: a file // that cannot be honored fails the whole session closed. - let (policy, policy_warnings) = - match SelectionPolicy::load(&memory_policy_fs(&files, &options.policy_paths), &options.policy_overrides) { - Ok(loaded) => loaded, - Err(error) => { - return Ok(policy_error_output(&error, warnings, files_input, bytes_input)); - } - }; + let (policy, policy_warnings) = match SelectionPolicy::load( + &memory_policy_fs(&files, &options.policy_paths), + &options.policy_overrides, + ) { + Ok(loaded) => loaded, + Err(error) => { + return Ok(policy_error_output( + &error, + warnings, + files_input, + bytes_input, + )); + } + }; // Path selection chose which files to send by the policy it read; a // different policy here would judge roots it never fetched. let read = match policy.source() { @@ -465,16 +469,27 @@ async fn engine( }; // Selection returns no digest when it bypassed the file, so a digest // with a bypassed session means the two sides disagree. - let expected = if options.policy_overrides.bypass { None } else { read.map(|(_, sha)| sha) }; + let expected = if options.policy_overrides.bypass { + None + } else { + read.map(|(_, sha)| sha) + }; if expected != options.policy_sha256.as_deref() { let error = PolicyError::Invalid { - file: read.map_or(POLICY_FILE_NAMES[0], |(path, _)| path).to_string(), + file: read + .map_or(POLICY_FILE_NAMES[0], |(path, _)| path) + .to_string(), key: String::new(), message: "the policy content differs from the one path selection read: pass \ selectHostedScanPaths' policySha256 and stream the same text" .to_string(), }; - return Ok(policy_error_output(&error, warnings, files_input, bytes_input)); + return Ok(policy_error_output( + &error, + warnings, + files_input, + bytes_input, + )); } for w in policy_warnings { warnings.push(EngineWarning::new(w.code, w.detail, None)); @@ -722,23 +737,25 @@ async fn engine( // the tree's manifest and vendor ledger, and the hosted pins its // lockfiles name. ALREADY rows carry the recorded uuid, so a re-scan // re-confirms a pin instead of swapping it. - let mut stage = Stage::new(options.max_new(policy.max_new_patches()), None, std::path::Path::new("")); + let mut stage = Stage::new( + options.max_new(policy.max_new_patches()), + None, + std::path::Path::new(""), + ); // A root whose every lookup failed hides packages that could have been // NEW: a capped run then admits none anywhere (§5.2). - stage.incomplete |= states - .iter() - .any(|s| s.error.as_ref().is_some_and(|e| e.code == "patch_lookup_failed")); + stage.incomplete |= states.iter().any(|s| { + s.error + .as_ref() + .is_some_and(|e| e.code == "patch_lookup_failed") + }); let roots_by_path: Vec = states.iter().map(|s| s.root.clone()).collect(); for state in states.iter_mut().filter(|s| s.error.is_none()) { let Some(project) = state.project.as_ref() else { continue; }; let recorded = memory_recorded(project, &state.root, &roots_by_path, &state.offers); - stage.incomplete |= lookup_incomplete( - &recorded, - &state.failed_details, - batch_failed, - ); + stage.incomplete |= lookup_incomplete(&recorded, &state.failed_details, batch_failed); let mut rows = classify(&state.offers, &recorded, &state.root); for row in &mut rows { row.candidate.in_flight = options.in_flight.contains(&row.candidate.base_purl); @@ -873,8 +890,11 @@ async fn engine( unknown_roots.contains(&row.candidate.project) || confirmed.contains(&(row.candidate.project.clone(), row.writer.uuid.clone())) }); - let deferred_rows: Vec<(crate::rollout::Candidate, u32)> = - stage.plan.as_ref().map(|p| p.deferred.clone()).unwrap_or_default(); + let deferred_rows: Vec<(crate::rollout::Candidate, u32)> = stage + .plan + .as_ref() + .map(|p| p.deferred.clone()) + .unwrap_or_default(); if !deferred_rows.is_empty() { let root_index: BTreeMap = states .iter() @@ -1126,7 +1146,10 @@ fn select_with_policy( let mut by_purl: BTreeMap> = BTreeMap::new(); for (patch, reason) in dropped { if !chosen.contains(patch.purl.as_str()) { - by_purl.entry(patch.purl.clone()).or_default().push((patch, reason)); + by_purl + .entry(patch.purl.clone()) + .or_default() + .push((patch, reason)); } } for (purl, mut group) in by_purl { diff --git a/crates/socket-patch-core/src/hosted/memory/roots.rs b/crates/socket-patch-core/src/hosted/memory/roots.rs index 35f39be1a..1a68e6528 100644 --- a/crates/socket-patch-core/src/hosted/memory/roots.rs +++ b/crates/socket-patch-core/src/hosted/memory/roots.rs @@ -31,17 +31,23 @@ pub const UNSUPPORTED_MARKERS: [(&str, &[&str]); 2] = [ /// trees, VCS and tool state, and vendored dependencies. Structural, so no /// policy can negate them. (Test and fixture trees are the socket.yml /// policy's overridable built-in ignores.) -pub(crate) const EXCLUDED_ROOT_SEGMENTS: [&str; 5] = ["node_modules", ".git", ".socket", ".yarn", "vendor"]; +pub(crate) const EXCLUDED_ROOT_SEGMENTS: [&str; 5] = + ["node_modules", ".git", ".socket", ".yarn", "vendor"]; /// The marker basenames of `root` among `paths` (the files the policy's /// path filters test for that root). -pub(crate) fn root_markers<'a>(root: &str, paths: impl IntoIterator) -> Vec { +pub(crate) fn root_markers<'a>( + root: &str, + paths: impl IntoIterator, +) -> Vec { let mut out: Vec = paths .into_iter() .filter_map(|path| { let (dir, base) = split_path(path); let marker = marker_ecosystem(base).is_some() - || UNSUPPORTED_MARKERS.iter().any(|(_, names)| names.contains(&base)); + || UNSUPPORTED_MARKERS + .iter() + .any(|(_, names)| names.contains(&base)); (dir == root && marker).then(|| base.to_string()) }) .collect(); @@ -202,7 +208,15 @@ mod tests { #[test] fn root_markers_name_every_marker_of_the_root_only() { assert_eq!( - root_markers("a", ["a/yarn.lock", "a/package.json", "a/b/yarn.lock", "a/pom.xml"]), + root_markers( + "a", + [ + "a/yarn.lock", + "a/package.json", + "a/b/yarn.lock", + "a/pom.xml" + ] + ), vec!["pom.xml".to_string(), "yarn.lock".to_string()] ); } diff --git a/crates/socket-patch-core/src/hosted/memory/select.rs b/crates/socket-patch-core/src/hosted/memory/select.rs index f18efa81e..04dcee403 100644 --- a/crates/socket-patch-core/src/hosted/memory/select.rs +++ b/crates/socket-patch-core/src/hosted/memory/select.rs @@ -15,8 +15,8 @@ use crate::patch::redirect::npmrc::NPMRC_REL; use crate::utils::python_lock::is_python_lock_name; use crate::policy::{ - MemoryPolicyFs, PolicyOverrides, PolicySource, Root, RootFile, SelectionPolicy, POLICY_FILE_NAMES, - SOCKET_YML_INVALID, + MemoryPolicyFs, PolicyOverrides, PolicySource, Root, RootFile, SelectionPolicy, + POLICY_FILE_NAMES, SOCKET_YML_INVALID, }; use super::roots::{ @@ -185,7 +185,10 @@ fn classify(rel: &str, root_files: &BTreeSet<&str>) -> Option { /// The listed root policy files with the text the caller fetched first. A /// listed file with no text (not passed, `missing`, or a symlink) is present /// without content, so loading it fails closed. -fn selection_policy_fs(blobs: &BTreeMap, supplied: &[PolicyFileInput]) -> MemoryPolicyFs { +fn selection_policy_fs( + blobs: &BTreeMap, + supplied: &[PolicyFileInput], +) -> MemoryPolicyFs { let mut fs = MemoryPolicyFs::default(); for name in POLICY_FILE_NAMES { let Some(&symlink) = blobs.get(name) else { @@ -211,7 +214,10 @@ fn selection_policy( options: &SelectOptions, ) -> Result { let supplied = options.policy_files.as_deref().unwrap_or_default(); - if let Some(bad) = supplied.iter().find(|f| !POLICY_FILE_NAMES.contains(&f.path.as_str())) { + if let Some(bad) = supplied + .iter() + .find(|f| !POLICY_FILE_NAMES.contains(&f.path.as_str())) + { return Err(PolicyErrorInfo { code: SOCKET_YML_INVALID.to_string(), detail: format!( diff --git a/crates/socket-patch-core/src/hosted/memory/types.rs b/crates/socket-patch-core/src/hosted/memory/types.rs index 2a11daed7..fa81876e8 100644 --- a/crates/socket-patch-core/src/hosted/memory/types.rs +++ b/crates/socket-patch-core/src/hosted/memory/types.rs @@ -171,7 +171,9 @@ impl<'de> Deserialize<'de> for MaxNewPatchesOption { if v == "none" { Ok(MaxNewPatchesOption(None)) } else { - Err(E::custom(format!("maxNewPatches must be a number or \"none\", not `{v}`"))) + Err(E::custom(format!( + "maxNewPatches must be a number or \"none\", not `{v}`" + ))) } } } diff --git a/crates/socket-patch-core/src/ledgers.rs b/crates/socket-patch-core/src/ledgers.rs index 9bcf56623..582ca464f 100644 --- a/crates/socket-patch-core/src/ledgers.rs +++ b/crates/socket-patch-core/src/ledgers.rs @@ -371,7 +371,6 @@ pub fn uuid_only_record(uuid: &str) -> PatchRecord { } } - /// Fold the hosted pins and the vendor ledger's patch records into the /// manifest view update detection consults. Hosted mode records purl→uuid /// ONLY in the lockfiles (`hosted_pins`, uuid only; v5 keeps no hosted diff --git a/crates/socket-patch-core/src/lib.rs b/crates/socket-patch-core/src/lib.rs index 143eae979..c15fe9e64 100644 --- a/crates/socket-patch-core/src/lib.rs +++ b/crates/socket-patch-core/src/lib.rs @@ -16,7 +16,6 @@ pub mod utils; pub mod vendor; pub mod vex; - #[cfg(test)] mod golden; #[cfg(test)] diff --git a/crates/socket-patch-core/src/manifest/records.rs b/crates/socket-patch-core/src/manifest/records.rs index 453e0ab2f..7032d435c 100644 --- a/crates/socket-patch-core/src/manifest/records.rs +++ b/crates/socket-patch-core/src/manifest/records.rs @@ -32,7 +32,10 @@ pub fn vulnerabilities_for_manifest( /// `patch`. `files` is the (purl-keyed) before/after-hash map the /// caller built — semantics for what counts as a "patchable file" differ /// between the get and download flows, so the caller owns that decision. -pub fn build_patch_record(patch: &PatchResponse, files: HashMap) -> PatchRecord { +pub fn build_patch_record( + patch: &PatchResponse, + files: HashMap, +) -> PatchRecord { PatchRecord { uuid: patch.uuid.clone(), exported_at: patch.published_at.clone(), diff --git a/crates/socket-patch-core/src/patch/redirect/cargo_lock_equivalence_tests.rs b/crates/socket-patch-core/src/patch/redirect/cargo_lock_equivalence_tests.rs index 082849761..5863631df 100644 --- a/crates/socket-patch-core/src/patch/redirect/cargo_lock_equivalence_tests.rs +++ b/crates/socket-patch-core/src/patch/redirect/cargo_lock_equivalence_tests.rs @@ -97,7 +97,6 @@ fn synth_lock(rng: &mut Rng, blocks: usize, v1: bool) -> String { out } - const INDEX: &str = "sparse+https://socket.example/cargo/index/"; fn plan_new(lock: &str, name: &str, version: &str, cksum: &str) -> CargoLockPlan { @@ -182,7 +181,8 @@ fn span_splice_matches_golden_on_hand_written_locks() { "[root]\nname = \"app\"\nversion = \"0.1.0\"\ndependencies = [\n \"d 1.0.0 ({crates_io})\",\n]\n\n[[package]]\nname = \"d\"\nversion = \"1.0.0\"\nsource = \"{crates_io}\"\n\n[[package]]\nname = \"u\"\nversion = \"2.0.0\"\nsource = \"{crates_io}\"\ndependencies = [\n \"d 1.0.0 ({crates_io})\",\n]\n\n[metadata]\n\"checksum d 1.0.0 ({crates_io})\" = \"cc\"\n\"checksum u 2.0.0 ({crates_io})\" = \"dd\"\n" ); let sourceless_v1 = "[[package]]\nname = \"s\"\nversion = \"1.0.0\"\n\n[metadata]\n\"checksum s 1.0.0 (registry+x)\" = \"ee\"\n".to_string(); - let source_at_eof = format!("[[package]]\nname = \"e\"\nversion = \"1.0.0\"\nsource = \"{crates_io}\""); + let source_at_eof = + format!("[[package]]\nname = \"e\"\nversion = \"1.0.0\"\nsource = \"{crates_io}\""); let bare = "version = 3\n\n[[package]]\nname = \"b\"\nversion = \"1.0.0\"\n\n[[package]]\nname = \"c\"\nversion = \"1.0.0\"\n".to_string(); let mut g = Golden::new( "cargo_lock_hand_written", diff --git a/crates/socket-patch-core/src/patch/redirect/golang_equivalence_tests.rs b/crates/socket-patch-core/src/patch/redirect/golang_equivalence_tests.rs index 3a8ebf5c2..075f630b4 100644 --- a/crates/socket-patch-core/src/patch/redirect/golang_equivalence_tests.rs +++ b/crates/socket-patch-core/src/patch/redirect/golang_equivalence_tests.rs @@ -10,7 +10,11 @@ use super::*; use crate::golden::Golden; use crate::test_rng::Rng; -fn run(g: &mut Golden, files: &BTreeMap, overrides: &[DepOverride]) -> RewriteResult { +fn run( + g: &mut Golden, + files: &BTreeMap, + overrides: &[DepOverride], +) -> RewriteResult { let mut got = RewriteResult::default(); rewrite_golang(files, overrides, &mut got); g.next(&(files, overrides), &got); diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index 72c8b2967..19516bdec 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -48,41 +48,41 @@ mod pdm; mod pipenv; pub mod presence; // The pnpm hosted planner lives with the format's model. -use crate::formats::pnpm::plan_hosted; +use crate::formats::cargo::hosted::CargoLockPlan; +#[cfg(test)] +use crate::formats::cargo::hosted::CARGO_LOCK_REFERENCE_KIND; use crate::formats::cargo::CargoLock; use crate::formats::composer::hosted::rewrite_composer_lock; use crate::formats::gem::gemfile; use crate::formats::gem::hosted::{checksum_entry_span, converge_gem_lock_source}; use crate::formats::gem::lock_lists_direct_dependency; -pub(crate) use crate::formats::yarn::is_berry_lock; -use crate::formats::cargo::hosted::CargoLockPlan; -#[cfg(test)] -use crate::formats::cargo::hosted::CARGO_LOCK_REFERENCE_KIND; #[cfg(test)] use crate::formats::pnpm::hosted::pnpm_unrewritten_instances; +use crate::formats::pnpm::plan_hosted; use crate::formats::yarn::berry_entry::{manifest_bin, render_pinned_entry, Pin}; +pub(crate) use crate::formats::yarn::is_berry_lock; +pub mod gradle; #[cfg(test)] mod pnpm_equivalence_tests; mod poetry; #[cfg(test)] mod python_lock_equivalence_tests; mod requirements; -pub mod gradle; pub use requirements::preflight_requirements_takeover; +pub(crate) mod hosted_url; mod staged; mod state; -pub(crate) mod hosted_url; pub mod upstream; pub mod vlt; pub mod vlt_heal; pub mod vlt_preflight; -pub use state::{ - load_redirect_state, save_redirect_state, - CorruptRedirectState, RedirectState, REDIRECT_STATE_REL, -}; /// Hosted-artifact leaf ownership rule, shared with `vex`'s bun lockfile /// discovery (which recovers a URL tuple's version from that leaf). pub(crate) use hosted_url::{hosted_url_names, hosted_url_version}; +pub use state::{ + load_redirect_state, save_redirect_state, CorruptRedirectState, RedirectState, + REDIRECT_STATE_REL, +}; /// One ecosystem's integrity hashes (mirrors the TS `PatchArtifactIntegrity`). #[derive(Debug, Clone, Default, Deserialize)] @@ -4009,7 +4009,12 @@ fn rewrite_yarn_berry_with_manifests( result.edits.push(FileEdit { path: BERRY_MANIFEST.into(), kind: "redirect_yarn_berry_resolution".into(), - action: if original.is_some() { "rewritten" } else { "added" }.into(), + action: if original.is_some() { + "rewritten" + } else { + "added" + } + .into(), key: Some(selector), original: original.map(Value::String), new: Some(Value::String(dep.artifact_url.clone())), @@ -4251,7 +4256,10 @@ impl BerryResolutionsPin { } let mut changed = Vec::new(); for selector in &self.selectors { - let previous = table.get(selector).and_then(Value::as_str).map(str::to_string); + let previous = table + .get(selector) + .and_then(Value::as_str) + .map(str::to_string); if previous.as_deref() != Some(url) { table.insert(selector.clone(), Value::String(url.to_string())); changed.push((selector.clone(), previous)); @@ -4433,7 +4441,11 @@ fn berry_catalog_selectors(yarnrc: Option<&str>, name: &str, ranges: &[&str]) -> /// order before the edit (`was_sorted`, from [`berry_entries_sorted`]; a /// hand-edited lock) keeps the entry in place, so a pin and its rollback /// still round-trip byte-exactly. -pub(crate) fn berry_reposition_blocks(blocks: &mut Vec, moved: &[String], was_sorted: bool) { +pub(crate) fn berry_reposition_blocks( + blocks: &mut Vec, + moved: &[String], + was_sorted: bool, +) { if !was_sorted { return; } @@ -4458,7 +4470,6 @@ pub(crate) fn berry_reposition_blocks(blocks: &mut Vec, moved: &[String] } } - // ── bun.lock (text lockfile) ───────────────────────────────────────────────── // A registry 4-tuple `["name@version", "", {deps}, "sha512-…"]` is // rewritten to a URL 3-tuple `["name@", {deps verbatim}, @@ -5059,7 +5070,6 @@ fn rewrite_uv_lock( } } - // ── composer.lock ──────────────────────────────────────────────────────────── /// Whether `text` points at `artifact_url` in any spelling a rewritten file may /// carry: the raw url every rewriter emits — composer.lock included, since @@ -8069,7 +8079,10 @@ mod tests { let files = BTreeMap::from([("nuget.config".into(), config)]); let result = rewrite_registry_redirect(&files, &[nuget_override()]); let out = result.files.get("nuget.config").expect("config rewritten"); - assert!(out.contains(&source), "original source bytes preserved: {out}"); + assert!( + out.contains(&source), + "original source bytes preserved: {out}" + ); // XML normalizes literal attribute whitespace to spaces, but // preserves character references. The fallback must keep the // same source identity under a real XML reader, not just ours. @@ -8626,7 +8639,11 @@ mod tests { #[test] fn yarn_berry_hosted_pin_routes_resolutions_to_a_tarball_entry() { let checksum = format!("10c0/{}", "7".repeat(128)); - let scoped_url = berry_hosted_url("@isaacs/string-locale-compare", "string-locale-compare", "1.1.0"); + let scoped_url = berry_hosted_url( + "@isaacs/string-locale-compare", + "string-locale-compare", + "1.1.0", + ); let plain_url = berry_hosted_url("left-pad", "left-pad", "1.3.0"); let scoped = DepOverride { namespace: Some("@isaacs".into()), @@ -8659,8 +8676,14 @@ mod tests { )), "unscoped entry re-keyed to its tarball: {out}" ); - assert!(!out.contains("__archiveUrl") && !out.contains("@npm:"), "{out}"); - assert!(out.ends_with("linkType: hard\n"), "trailing newline kept: {out:?}"); + assert!( + !out.contains("__archiveUrl") && !out.contains("@npm:"), + "{out}" + ); + assert!( + out.ends_with("linkType: hard\n"), + "trailing newline kept: {out:?}" + ); let manifest: Value = serde_json::from_str(&r.files["package.json"]).unwrap(); assert_eq!( manifest["resolutions"], @@ -8672,11 +8695,17 @@ mod tests { ); assert_eq!(manifest["name"], "app", "the rest of the manifest is kept"); assert_eq!( - r.edits.iter().filter(|e| e.kind == "redirect_yarn_berry_entry").count(), + r.edits + .iter() + .filter(|e| e.kind == "redirect_yarn_berry_entry") + .count(), 2 ); assert_eq!( - r.edits.iter().filter(|e| e.kind == "redirect_yarn_berry_resolution").count(), + r.edits + .iter() + .filter(|e| e.kind == "redirect_yarn_berry_resolution") + .count(), 2 ); } @@ -8909,10 +8938,7 @@ mod tests { rewrite_yarn_berry(&files, std::slice::from_ref(&ovr), &mut r); assert!(r.warnings.is_empty(), "{:?}", r.warnings); let out = &r.files["yarn.lock"]; - let keys: Vec<&str> = out - .lines() - .filter(|l| l.starts_with('"')) - .collect(); + let keys: Vec<&str> = out.lines().filter(|l| l.starts_with('"')).collect(); assert_eq!( keys, vec![ @@ -8956,7 +8982,11 @@ mod tests { let mut again = RewriteResult::default(); rewrite_yarn_berry(&pinned, std::slice::from_ref(&ovr), &mut again); assert!(again.warnings.is_empty(), "{:?}", again.warnings); - assert!(again.files.is_empty(), "repeat run rewrites nothing: {:?}", again.files); + assert!( + again.files.is_empty(), + "repeat run rewrites nothing: {:?}", + again.files + ); // A pin already complete is confirmed without a write. assert!(again.confirmed_yarn_berry_uuids.contains(BERRY_UUID)); @@ -8969,7 +8999,10 @@ mod tests { assert!(out.contains(&format!("\"left-pad@{new_url}\":")), "{out}"); assert!(!out.contains(BERRY_UUID), "{out}"); let manifest: Value = serde_json::from_str(&repin.files["package.json"]).unwrap(); - assert_eq!(manifest["resolutions"], json!({"left-pad@npm:^1.3.0": new_url})); + assert_eq!( + manifest["resolutions"], + json!({"left-pad@npm:^1.3.0": new_url}) + ); } /// The URL-keyed lock entry alone is half a pin: with its manifest @@ -9216,7 +9249,9 @@ mod tests { assert!(r.warnings.is_empty(), "{:?}", r.warnings); let out = &r.files["yarn.lock"]; assert!( - out.contains(&format!("\"left-pad@{url}\":\n version: 1.3.0\n resolution: \"left-pad@{url}\"\n")), + out.contains(&format!( + "\"left-pad@{url}\":\n version: 1.3.0\n resolution: \"left-pad@{url}\"\n" + )), "{out}" ); assert!(!out.contains("__archiveUrl"), "{out}"); @@ -9242,10 +9277,17 @@ mod tests { "{{\n \"name\": \"app\",\n \"resolutions\": {{\n \"{selector}\": \"1.3.0\"\n }}\n}}\n" ); let mut r = RewriteResult::default(); - rewrite_yarn_berry(&berry_files(berry_lock("10c0"), manifest), std::slice::from_ref(&ovr), &mut r); + rewrite_yarn_berry( + &berry_files(berry_lock("10c0"), manifest), + std::slice::from_ref(&ovr), + &mut r, + ); assert!(r.files.is_empty(), "{label}: {:?}", r.files); assert_eq!( - r.warnings.iter().map(|w| w.code.as_str()).collect::>(), + r.warnings + .iter() + .map(|w| w.code.as_str()) + .collect::>(), vec!["redirect_yarn_berry_resolutions_conflict"], "{label}" ); @@ -9270,12 +9312,20 @@ mod tests { "mirror tarball" ); // An unrelated user entry is kept as-is next to ours. - let manifest = "{\n \"name\": \"app\",\n \"resolutions\": {\n \"other\": \"2.0.0\"\n }\n}\n"; + let manifest = + "{\n \"name\": \"app\",\n \"resolutions\": {\n \"other\": \"2.0.0\"\n }\n}\n"; let mut r = RewriteResult::default(); - rewrite_yarn_berry(&berry_files(berry_lock("10c0"), manifest.into()), std::slice::from_ref(&ovr), &mut r); + rewrite_yarn_berry( + &berry_files(berry_lock("10c0"), manifest.into()), + std::slice::from_ref(&ovr), + &mut r, + ); assert!(r.warnings.is_empty(), "{:?}", r.warnings); let m: Value = serde_json::from_str(&r.files["package.json"]).unwrap(); - assert_eq!(m["resolutions"], json!({"other": "2.0.0", "left-pad@npm:^1.3.0": url})); + assert_eq!( + m["resolutions"], + json!({"other": "2.0.0", "left-pad@npm:^1.3.0": url}) + ); let mut files = BTreeMap::new(); files.insert("yarn.lock".to_string(), berry_lock("10c0")); @@ -9283,7 +9333,10 @@ mod tests { rewrite_yarn_berry(&files, std::slice::from_ref(&ovr), &mut r); assert!(r.files.is_empty(), "{:?}", r.files); assert_eq!( - r.warnings.iter().map(|w| w.code.as_str()).collect::>(), + r.warnings + .iter() + .map(|w| w.code.as_str()) + .collect::>(), vec!["redirect_yarn_berry_manifest_missing"] ); @@ -9294,10 +9347,17 @@ mod tests { berry_lock("10c0") ); let mut r = RewriteResult::default(); - rewrite_yarn_berry(&berry_files(with_patch, berry_manifest()), std::slice::from_ref(&ovr), &mut r); + rewrite_yarn_berry( + &berry_files(with_patch, berry_manifest()), + std::slice::from_ref(&ovr), + &mut r, + ); assert!(r.files.is_empty(), "{:?}", r.files); let codes: Vec<&str> = r.warnings.iter().map(|w| w.code.as_str()).collect(); - assert!(codes.contains(&"redirect_yarn_berry_shared_descriptor"), "{codes:?}"); + assert!( + codes.contains(&"redirect_yarn_berry_shared_descriptor"), + "{codes:?}" + ); } /// Yarn routes a URL locator to its tarball fetcher only when it is an @@ -9322,7 +9382,10 @@ mod tests { assert!(r.files.is_empty(), "{url}: nothing written"); assert!(r.edits.is_empty(), "{url}: {:?}", r.edits); assert_eq!( - r.warnings.iter().map(|w| w.code.as_str()).collect::>(), + r.warnings + .iter() + .map(|w| w.code.as_str()) + .collect::>(), vec!["redirect_yarn_berry_artifact_url_unsupported"], "{url}" ); @@ -12586,7 +12649,11 @@ mod tests { let out = r.files.get("Gemfile.lock").expect("lock rewritten"); let rows: Vec<&str> = out .lines() - .filter(|l| l.trim_start().starts_with("rails (7.0.0)") && l.starts_with(" ") && !l.starts_with(" ")) + .filter(|l| { + l.trim_start().starts_with("rails (7.0.0)") + && l.starts_with(" ") + && !l.starts_with(" ") + }) .collect(); assert_eq!( rows, @@ -12599,7 +12666,11 @@ mod tests { "{entry}: the entry keeps its line ending: {out:?}" ); let model = crate::formats::gem::GemfileLock::parse(out); - assert_eq!(model.checksum("rails", "7.0.0"), Some(patched.as_str()), "{entry}"); + assert_eq!( + model.checksum("rails", "7.0.0"), + Some(patched.as_str()), + "{entry}" + ); assert!(!out.contains("\r\r"), "line endings kept: {out:?}"); let edit = r .edits @@ -12614,7 +12685,10 @@ mod tests { files.insert("Gemfile.lock".to_string(), out.clone()); let again = rewrite_registry_redirect(&files, &[gem_override("rails", "7.0.0")]); assert!( - !again.edits.iter().any(|e| e.kind == "redirect_gemfile_lock_checksum"), + !again + .edits + .iter() + .any(|e| e.kind == "redirect_gemfile_lock_checksum"), "{entry}: rerun is a no-op: {:?}", again.edits ); @@ -13118,11 +13192,19 @@ mod tests { let redacted = format!( "https://patch.socket.dev/patch/npm/left-pad/1.3.0//{uuid}/left-pad-1.3.0.tgz?x=1" ); - assert_eq!(redact_grant_token(&url, &url, uuid), redacted, "the URL alone"); + assert_eq!( + redact_grant_token(&url, &url, uuid), + redacted, + "the URL alone" + ); let text = format!("vlt would fail to verify {url}: fetch error GET {url}: reset"); - let want = format!("vlt would fail to verify {redacted}: fetch error GET {redacted}: reset"); + let want = + format!("vlt would fail to verify {redacted}: fetch error GET {redacted}: reset"); assert_eq!(redact_grant_token(&text, &url, uuid), want, "every quote"); - assert!(!redact_grant_token(&text, &url, uuid).contains(token), "no token left"); + assert!( + !redact_grant_token(&text, &url, uuid).contains(token), + "no token left" + ); let registry = format!("https://patch.socket.dev/patch-registry/npm/{token}/{uuid}"); assert_eq!( redact_grant_token(®istry, ®istry, uuid), @@ -14877,7 +14959,10 @@ mod tests { ("crlf", lf.replace('\n', "\r\n")), ("tabs", lf.replace(" ", "\t")), ("bom", format!("\u{feff}{lf}")), - ("bom+crlf+tabs", format!("\u{feff}{}", lf.replace(" ", "\t").replace('\n', "\r\n"))), + ( + "bom+crlf+tabs", + format!("\u{feff}{}", lf.replace(" ", "\t").replace('\n', "\r\n")), + ), ]; for (shape, pristine) in shapes { let mut files = BTreeMap::new(); @@ -14893,7 +14978,10 @@ mod tests { "http://patch.test/left-pad-1.3.0.tgz", ) .replace("sha512-UPSTREAM==", "sha512-PATCHED=="); - assert_eq!(out, &expected, "{shape}: only the rewired values may change"); + assert_eq!( + out, &expected, + "{shape}: only the rewired values may change" + ); } } @@ -17237,7 +17325,8 @@ packages: ); // One edit; its fragments are the on-disk bytes of the entry. - let lock_edits: Vec<&FileEdit> = r.edits.iter().filter(|e| e.path == "yarn.lock").collect(); + let lock_edits: Vec<&FileEdit> = + r.edits.iter().filter(|e| e.path == "yarn.lock").collect(); assert_eq!(lock_edits.len(), 1, "{label}"); let edit = lock_edits[0]; let (orig, new) = ( @@ -17247,15 +17336,8 @@ packages: assert_eq!( (orig, new), ( - respell( - lf_edit - .original - .as_ref() - .unwrap() - .as_str() - .unwrap() - ) - .trim_start_matches('\u{feff}'), + respell(lf_edit.original.as_ref().unwrap().as_str().unwrap()) + .trim_start_matches('\u{feff}'), respell(lf_edit.new.as_ref().unwrap().as_str().unwrap()) .trim_start_matches('\u{feff}'), ), @@ -19326,7 +19408,11 @@ packages: format!( "__metadata:\n version: 8\n cacheKey: 10c0\n\n{key}:\n version: 9.0.1\n \ resolution: \"x\"\n{} languageName: node\n linkType: hard\n", - if bin { " bin:\n uuid: dist/bin/uuid\n" } else { "" } + if bin { + " bin:\n uuid: dist/bin/uuid\n" + } else { + "" + } ) }; let needs = |lock: String| berry_pin_needs_manifest(&berry_bin_entries(&lock), &dep); @@ -19337,9 +19423,14 @@ packages: assert!(!needs(entry("\"uuid@npm:other-uuid@^9.0.0\"", true))); assert!(!needs(entry("\"uuid@npm:^9.0.0, other@npm:^1.0.0\"", true))); assert!(!needs(entry("\"uuid@patch:uuid@npm%3A9.0.1#x\"", true))); - assert!(!needs(entry("\"uuid@https://mirror.example/uuid-9.0.1.tgz\"", true))); + assert!(!needs(entry( + "\"uuid@https://mirror.example/uuid-9.0.1.tgz\"", + true + ))); // Another version of the package (`9.0.10` shares the prefix). - assert!(!needs(entry("\"uuid@npm:^9.0.0\"", true).replace("9.0.1\n", "9.0.10\n"))); + assert!(!needs( + entry("\"uuid@npm:^9.0.0\"", true).replace("9.0.1\n", "9.0.10\n") + )); } /// A bun URL 3-tuple already at the CURRENT artifact URL but with a stale diff --git a/crates/socket-patch-core/src/patch/redirect/npmrc.rs b/crates/socket-patch-core/src/patch/redirect/npmrc.rs index ac102ef78..6a9c4a17a 100644 --- a/crates/socket-patch-core/src/patch/redirect/npmrc.rs +++ b/crates/socket-patch-core/src/patch/redirect/npmrc.rs @@ -33,8 +33,6 @@ //! and — when the project file is silent — the user / global / builtin //! config files ([`resolve_outer_allow_remote`]). - - /// Repo-relative path of the project `.npmrc` the auto-config edits. pub const NPMRC_REL: &str = ".npmrc"; @@ -1137,5 +1135,4 @@ mod tests { ); } } - } diff --git a/crates/socket-patch-core/src/patch/redirect/pdm.rs b/crates/socket-patch-core/src/patch/redirect/pdm.rs index 95d910f23..80e0eb6b7 100644 --- a/crates/socket-patch-core/src/patch/redirect/pdm.rs +++ b/crates/socket-patch-core/src/patch/redirect/pdm.rs @@ -235,9 +235,18 @@ mod tests { #[test] fn legacy_formats_warn_stale_install_risk_once() { for (fixture, warns) in [ - (include_str!("../../../tests/fixtures/pdm-native/0.12.3.lock"), true), - (include_str!("../../../tests/fixtures/pdm-native/2.8.2.lock"), true), - (include_str!("../../../tests/fixtures/pdm-native/2.29.2.lock"), false), + ( + include_str!("../../../tests/fixtures/pdm-native/0.12.3.lock"), + true, + ), + ( + include_str!("../../../tests/fixtures/pdm-native/2.8.2.lock"), + true, + ), + ( + include_str!("../../../tests/fixtures/pdm-native/2.29.2.lock"), + false, + ), ] { let mut result = RewriteResult::default(); rewrite( @@ -410,7 +419,11 @@ mod parse_reuse_equivalence_tests { let what = format!("{fixture} extra={extra} crlf={crlf}"); let mut got = RewriteResult::default(); rewrite(&files, &deps, &mut got); - g.case(what.replace(' ', "/"), &(&files, &deps), &format!("{got:?}")); + g.case( + what.replace(' ', "/"), + &(&files, &deps), + &format!("{got:?}"), + ); confirmed += got.confirmed_pdm_uuids.len(); let mut again = files.clone(); @@ -428,4 +441,56 @@ mod parse_reuse_equivalence_tests { assert!(confirmed > 100, "only {confirmed} confirmed"); g.finish(); } + + /// A dozen patched packages in one lock cost one whole-lock render and + /// re-parse, not one per package (#762). + #[test] + fn many_patches_render_the_lock_once() { + use crate::utils::lock_fragments::RENDERS; + let url = |name: &str| { + format!("https://patch.socket.dev/patch/pypi/{name}/a/{name}-1.26.18-py3-none-any.whl") + }; + let mut checked = 0; + for (fixture, lock) in fixtures() { + for crlf in [false, true] { + let mut lock = grown(&lock.replace("\r\n", "\n"), 11); + if crlf { + lock = lock.replace('\n', "\r\n"); + } + let names = std::iter::once("urllib3".to_string()) + .chain((0..11).map(|n| format!("pkg{n}"))); + let deps: Vec = names + .enumerate() + .map(|(n, name)| DepOverride { + ecosystem: "pypi".into(), + artifact_url: url(&name), + name, + namespace: None, + version: "1.26.18".into(), + token: String::new(), + patch_uuid: format!("00000000-0000-4000-8000-{n:012}"), + registry_override: None, + integrity: Integrity { + sha256: Some("a".repeat(64)), + ..Default::default() + }, + }) + .collect(); + let files = BTreeMap::from([("pdm.lock".to_string(), lock)]); + RENDERS.with(|renders| renders.set(0)); + let mut got = RewriteResult::default(); + rewrite(&files, &deps, &mut got); + if got.confirmed_pdm_uuids.len() != 12 { + continue; // a generation this dep shape doesn't land on + } + checked += 1; + assert_eq!( + RENDERS.with(|renders| renders.get()), + 1, + "{fixture} crlf={crlf}" + ); + } + } + assert!(checked >= 20, "only {checked} locks landed every dep"); + } } diff --git a/crates/socket-patch-core/src/patch/redirect/pipenv.rs b/crates/socket-patch-core/src/patch/redirect/pipenv.rs index 0371ec923..c13b4a567 100644 --- a/crates/socket-patch-core/src/patch/redirect/pipenv.rs +++ b/crates/socket-patch-core/src/patch/redirect/pipenv.rs @@ -456,7 +456,10 @@ mod tests { let original = serde_json::to_string(&value).unwrap(); // A live lock (Pipfile beside it): conflicts veto the siblings. let files = BTreeMap::from([ - ("Pipfile".to_string(), "[packages]\nurllib3 = \"*\"\n".to_string()), + ( + "Pipfile".to_string(), + "[packages]\nurllib3 = \"*\"\n".to_string(), + ), ("Pipfile.lock".to_string(), original), ]); let mut result = RewriteResult::default(); @@ -496,20 +499,30 @@ mod tests { for stale in &stale_locks { let files = BTreeMap::from([ ("Pipfile.lock".to_string(), stale.clone()), - ("requirements.txt".to_string(), "urllib3==1.26.18\n".to_string()), + ( + "requirements.txt".to_string(), + "urllib3==1.26.18\n".to_string(), + ), ]); - let result = super::super::rewrite_registry_redirect(&files, std::slice::from_ref(&dep)); + let result = + super::super::rewrite_registry_redirect(&files, std::slice::from_ref(&dep)); assert!( !result.refused_pipenv_uuids.contains("patch-one"), "a non-conflict must not veto: {stale}" ); assert!( - result.warnings.iter().any(|w| w.code == "redirect_pipenv_skipped"), + result + .warnings + .iter() + .any(|w| w.code == "redirect_pipenv_skipped"), "{:?}", result.warnings ); assert!( - result.files.get("requirements.txt").is_some_and(|t| t.contains("patch.socket.dev")), + result + .files + .get("requirements.txt") + .is_some_and(|t| t.contains("patch.socket.dev")), "requirements.txt must still be redirected past a stale Pipfile.lock: {result:?}" ); assert!(!result.files.contains_key("Pipfile.lock")); @@ -567,7 +580,10 @@ mod tests { let files = |text: &str| BTreeMap::from([("Pipfile.lock".to_string(), text.to_string())]); assert!(lock_targets(&files(&lock()), std::slice::from_ref(&dep))); assert!(!lock_targets(&files(&lock()), std::slice::from_ref(&other))); - assert!(!lock_targets(&files("{ not json"), std::slice::from_ref(&dep))); + assert!(!lock_targets( + &files("{ not json"), + std::slice::from_ref(&dep) + )); assert!(!lock_targets(&BTreeMap::new(), std::slice::from_ref(&dep))); let mut npm = dep.clone(); npm.ecosystem = "npm".into(); @@ -593,7 +609,10 @@ mod tests { let entry: Value = serde_json::from_str(&fixed).unwrap(); assert!(entry["default"]["urllib3"].get("version").is_none()); assert_eq!(entry["default"]["urllib3"]["index"], json!("pypi")); - assert!(entry["default"]["urllib3"]["file"].as_str().unwrap().contains("patch-one")); + assert!(entry["default"]["urllib3"]["file"] + .as_str() + .unwrap() + .contains("patch-one")); value["default"]["urllib3"]["version"] = json!("==2.0.0"); let conflicting = serde_json::to_string(&value).unwrap(); @@ -614,7 +633,10 @@ mod tests { assert!(owned_url(public, &dep)); assert!(!owned_url("https://example.org/patch/pypi/urllib3/1.26.18/tok/patch-one/urllib3-1.26.18-py3-none-any.whl", &dep)); dep.artifact_url = "https://patches.internal.example:8443/patch/pypi/urllib3/1.26.18/tok/patch-one/urllib3-1.26.18-py3-none-any.whl".into(); - assert!(owned_url(&dep.artifact_url, &dep), "the grant's own origin is ours"); + assert!( + owned_url(&dep.artifact_url, &dep), + "the grant's own origin is ours" + ); assert!(owned_url(public, &dep), "and so is the public service"); assert!(!owned_url("https://patches.internal.example:8443/patch/pypi/urllib3/1.26.19/tok/patch-one/urllib3-1.26.19-py3-none-any.whl", &dep), "another version is not"); // Rotation on the custom origin re-points the owned entry. @@ -625,7 +647,6 @@ mod tests { assert!(second.contains("/rotated/") && !second.contains("/tok/")); } - /// Hosted Pipenv recognizes its own pins through the shared recognizer /// (#563): a path-prefixed `--patch-server-url` deployment rotates its /// grant instead of refusing its own previous reference, and a hosted @@ -699,7 +720,9 @@ mod compatibility_tests { assert!(!result.refused_pipenv_uuids.contains("patch-one")); assert!(result.files["requirements.txt"].contains("patch.socket.dev")); assert!(!result.files.contains_key("Pipfile.lock")); - assert!(result.warnings.iter().any(|w| w.code == "redirect_pipenv_refused" && w.detail.contains("no Pipfile"))); + assert!(result + .warnings + .iter() + .any(|w| w.code == "redirect_pipenv_refused" && w.detail.contains("no Pipfile"))); } - } diff --git a/crates/socket-patch-core/src/patch/redirect/poetry.rs b/crates/socket-patch-core/src/patch/redirect/poetry.rs index 624b74c4a..8a25c3807 100644 --- a/crates/socket-patch-core/src/patch/redirect/poetry.rs +++ b/crates/socket-patch-core/src/patch/redirect/poetry.rs @@ -92,7 +92,9 @@ pub(super) fn rewrite_poetry( } } Err(detail) => { - result.refused_python_lock_uuids.insert(dep.patch_uuid.clone()); + result + .refused_python_lock_uuids + .insert(dep.patch_uuid.clone()); result.warnings.push(RewriteWarning { code: "redirect_poetry_lock_unsupported".into(), detail: format!("{path}: {detail}"), @@ -100,7 +102,9 @@ pub(super) fn rewrite_poetry( continue; } } - result.confirmed_python_lock_uuids.insert(dep.patch_uuid.clone()); + result + .confirmed_python_lock_uuids + .insert(dep.patch_uuid.clone()); content = rewrite.text; if !stale_warned { if let Some(format) = @@ -136,14 +140,18 @@ pub(super) fn rewrite_poetry( } // Already redirected to this artifact (idempotent re-scan). Ok(Some(_)) => { - result.confirmed_python_lock_uuids.insert(dep.patch_uuid.clone()); + result + .confirmed_python_lock_uuids + .insert(dep.patch_uuid.clone()); } Ok(None) => result.warnings.push(RewriteWarning { code: "redirect_poetry_entry_not_found".into(), detail: format!("no {path} entry for {}@{}", dep.name, dep.version), }), Err(detail) => { - result.refused_python_lock_uuids.insert(dep.patch_uuid.clone()); + result + .refused_python_lock_uuids + .insert(dep.patch_uuid.clone()); result.warnings.push(RewriteWarning { code: "redirect_poetry_lock_unsupported".into(), detail: format!("{path}: {detail}"), @@ -268,10 +276,40 @@ mod equivalence_tests { let mut again = files.clone(); again.extend(got.files.clone()); let got = run(rewrite_poetry, &again, &deps); - g.case(format!("{what}/re-run"), &(&again, &deps), &format!("{got:?}")); + g.case( + format!("{what}/re-run"), + &(&again, &deps), + &format!("{got:?}"), + ); } } } g.finish(); } + + /// A dozen patched packages in one lock cost one whole-lock render and + /// re-parse, not one per package (#760). + #[test] + fn many_patches_render_the_lock_once() { + use crate::utils::lock_fragments::RENDERS; + for version in VERSIONS.iter().filter(|version| !version.starts_with("0.")) { + for crlf in [false, true] { + let mut lock = grown(version, 11); + if crlf { + lock = lock.replace('\n', "\r\n"); + } + let files = BTreeMap::from([("poetry.lock".to_string(), lock)]); + let mut deps = vec![dep("urllib3", "1.26.18", Some(SHA), 0)]; + deps.extend((0..11).map(|i| dep(&format!("pkg{i}"), "1.26.18", Some(SHA), i + 1))); + RENDERS.with(|renders| renders.set(0)); + let got = run(rewrite_poetry, &files, &deps); + assert_eq!(got.confirmed_python_lock_uuids.len(), 12, "{version}"); + assert_eq!( + RENDERS.with(|renders| renders.get()), + 1, + "{version} crlf={crlf}" + ); + } + } + } } diff --git a/crates/socket-patch-core/src/patch/redirect/state.rs b/crates/socket-patch-core/src/patch/redirect/state.rs index 6d1b2f5d0..98d0b620e 100644 --- a/crates/socket-patch-core/src/patch/redirect/state.rs +++ b/crates/socket-patch-core/src/patch/redirect/state.rs @@ -56,7 +56,6 @@ impl RedirectState { records: BTreeMap::new(), } } - } impl Default for RedirectState { @@ -518,5 +517,4 @@ mod tests { "changed bytes still go through the (here refused) atomic write" ); } - } diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/bun_lockb.rs b/crates/socket-patch-core/src/patch/redirect/upstream/bun_lockb.rs index f51142f69..87c49a542 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/bun_lockb.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/bun_lockb.rs @@ -332,7 +332,10 @@ mod tests { let package_start = u64::from_le_bytes(lock[110..118].try_into().unwrap()) as usize; // The root resolution's flag byte (its last). let flags_at = package_start + count * 16 + 63; - assert_eq!(lock[flags_at], crate::vendor::bun_lockb::NORMALIZED_FORMAT_1); + assert_eq!( + lock[flags_at], + crate::vendor::bun_lockb::NORMALIZED_FORMAT_1 + ); lock[flags_at] |= 0x40; BunLockb::parse(&lock).unwrap().validate_mutation().unwrap(); let (outcome, after) = run(&lock, &vendor_opts()).await; diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs b/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs index c44d7919e..70ca86a6d 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs @@ -97,7 +97,10 @@ pub(crate) async fn restore( ) }); let cksums: BTreeMap> = - futures_util::future::join_all(lookups).await.into_iter().collect(); + futures_util::future::join_all(lookups) + .await + .into_iter() + .collect(); let mut changed = false; let mut restored: Vec<(&LockHit, String)> = Vec::new(); for hit in &hits { @@ -116,7 +119,9 @@ pub(crate) async fn restore( } // The entries' own source + checksum values, spliced at the parse's // spans (every hit is a distinct block: its source names its uuid). - let spans = model.spans().expect("a lock parsed from text carries spans"); + let spans = model + .spans() + .expect("a lock parsed from text carries spans"); let mut splices: Vec<(std::ops::Range, String)> = Vec::new(); for (hit, cksum) in &restored { let at = &spans.packages[hit.index]; @@ -133,7 +138,10 @@ pub(crate) async fn restore( } for (hit, cksum) in &restored { // Dependents' full-id references and the v1 `[metadata]` key. - lock = lock.replace(&format!("({})", hit.source), &format!("({CRATES_IO_SOURCE})")); + lock = lock.replace( + &format!("({})", hit.source), + &format!("({CRATES_IO_SOURCE})"), + ); let metadata_key = format!( "\"checksum {} {} ({CRATES_IO_SOURCE})\" = \"", hit.name, hit.version @@ -152,7 +160,11 @@ pub(crate) async fn restore( if changed { view.write( "Cargo.lock", - if crlf { lock.replace('\n', "\r\n") } else { lock }, + if crlf { + lock.replace('\n', "\r\n") + } else { + lock + }, ); } } @@ -317,11 +329,10 @@ fn remove_registry_block(config: &str, reg: &str) -> Option { end -= 1; } let fragment = format!("{}\n", lines[i..end].join("\n")); - let removed = remove_appended_cargo_block(&lf, &fragment) - .or_else(|| { - // The block ends the file with no final newline. - remove_appended_cargo_block(&lf, fragment.trim_end_matches('\n')) - })?; + let removed = remove_appended_cargo_block(&lf, &fragment).or_else(|| { + // The block ends the file with no final newline. + remove_appended_cargo_block(&lf, fragment.trim_end_matches('\n')) + })?; Some(if crlf { removed.replace('\n', "\r\n") } else { @@ -388,7 +399,10 @@ mod tests { #[test] fn table_form_line_is_dropped() { - assert_eq!(unpin_line(&format!("registry = \"{REG}\""), REG), Some(None)); + assert_eq!( + unpin_line(&format!("registry = \"{REG}\""), REG), + Some(None) + ); } #[test] @@ -397,7 +411,10 @@ mod tests { let hosted = format!( "{original}\n[registries.{REG}]\nindex = \"sparse+https://patch.socket.dev/x/index/\"\n" ); - assert_eq!(remove_registry_block(&hosted, REG).as_deref(), Some(original)); + assert_eq!( + remove_registry_block(&hosted, REG).as_deref(), + Some(original) + ); let created = format!("[registries.{REG}]\nindex = \"sparse+https://x/\"\n"); assert_eq!(remove_registry_block(&created, REG).as_deref(), Some("")); } diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/gem.rs b/crates/socket-patch-core/src/patch/redirect/upstream/gem.rs index a20a3cb3c..c47227d7e 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/gem.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/gem.rs @@ -57,11 +57,11 @@ use std::collections::{BTreeMap, BTreeSet}; use regex::Regex; use super::{Ctx, FormatResult, HostedPin, View}; -use crate::utils::line_endings::{to_lf, LineEndings}; -use crate::vendor::gem::{gem_declaration_any, quoted_literal}; use crate::formats::gem::{ bundler_manifest_for, parse_spec, same_remote, split_checksum_entry, BUNDLER_LOCKS, }; +use crate::utils::line_endings::{to_lf, LineEndings}; +use crate::vendor::gem::{gem_declaration_any, quoted_literal}; /// The default upstream `GEM` remote. const RUBYGEMS_REMOTE: &str = "https://rubygems.org/"; @@ -250,17 +250,14 @@ fn choose_upstream( /// The line after which a spec named `name-version` sorts into `sec` /// (bundler writes specs sorted by full name). fn insertion_point(sec: &GemSec, full_name: &str) -> Option { - let pred = sec - .entries - .iter() - .rfind(|e| { - let full = if e.version.is_empty() { - e.name.clone() - } else { - format!("{}-{}", e.name, e.version) - }; - full.as_str() < full_name - }); + let pred = sec.entries.iter().rfind(|e| { + let full = if e.version.is_empty() { + e.name.clone() + } else { + format!("{}-{}", e.name, e.version) + }; + full.as_str() < full_name + }); pred.map(|e| e.last).or(sec.specs_line) } diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/golang.rs b/crates/socket-patch-core/src/patch/redirect/upstream/golang.rs index 4ce16051f..cee422040 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/golang.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/golang.rs @@ -65,7 +65,10 @@ pub(crate) async fn restore( (uuid.clone(), ctx.client.go_sums(module, version).await) }); let sums: std::collections::BTreeMap> = - futures_util::future::join_all(lookups).await.into_iter().collect(); + futures_util::future::join_all(lookups) + .await + .into_iter() + .collect(); let mut go_mod_next = go_mod.clone(); let mut go_sum = view.read("go.sum").await.ok().flatten(); @@ -88,8 +91,8 @@ pub(crate) async fn restore( } } if let Some(text) = go_sum.as_deref() { - let mut next = remove_module_prefix_lines(text, socket_module) - .unwrap_or_else(|| text.to_string()); + let mut next = + remove_module_prefix_lines(text, socket_module).unwrap_or_else(|| text.to_string()); let upstream = format!( "{module} {version} {}\n{module} {version}/go.mod {}\n", sums.zip_h1, sums.mod_h1 diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs b/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs index 8417b5a8b..3de39ee92 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs @@ -350,9 +350,7 @@ pub struct RestoreOutcome { impl RestoreOutcome { pub fn restored(&self) -> impl Iterator { - self.pins - .iter() - .filter(|p| p.status == PinStatus::Restored) + self.pins.iter().filter(|p| p.status == PinStatus::Restored) } pub fn refused(&self) -> impl Iterator { @@ -684,9 +682,7 @@ async fn restore_pass(view: &mut View<'_>, active: &[&HostedPin], ctx: &Ctx<'_>) Format::YarnLock => npm::restore_yarn_locks(view, &pins, &files, ctx).await, Format::PnpmLock => npm::restore_pnpm_locks(view, &pins, &files, ctx).await, Format::BunLock => npm::restore_bun_locks(view, &pins, &files, ctx).await, - Format::BunLockb if ctx.bun_lockb => { - bun_lockb::restore(view, &pins, &files, ctx).await - } + Format::BunLockb if ctx.bun_lockb => bun_lockb::restore(view, &pins, &files, ctx).await, Format::Cargo => cargo::restore(view, &pins, &files, ctx).await, Format::Golang => golang::restore(view, &pins, &files, ctx).await, Format::Gem => gem::restore(view, &pins, &files, ctx).await, diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/pypi_locks.rs b/crates/socket-patch-core/src/patch/redirect/upstream/pypi_locks.rs index ac105569f..8530ddf48 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/pypi_locks.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/pypi_locks.rs @@ -57,7 +57,16 @@ fn files_value(release: &[PypiFile], by_url: bool) -> Option { let key = if by_url { "url" } else { "file" }; let mut located: Vec<(&str, &PypiFile)> = release .iter() - .map(|f| (if by_url { f.url.as_str() } else { f.filename.as_str() }, f)) + .map(|f| { + ( + if by_url { + f.url.as_str() + } else { + f.filename.as_str() + }, + f, + ) + }) .collect(); // PDM orders each entry's files by the location it writes: a `static_urls` // lock by URL (so an sdist under `0c/…` precedes a wheel under `b0/…`), diff --git a/crates/socket-patch-core/src/policy/mod.rs b/crates/socket-patch-core/src/policy/mod.rs index 4e1fe9479..66fb3c863 100644 --- a/crates/socket-patch-core/src/policy/mod.rs +++ b/crates/socket-patch-core/src/policy/mod.rs @@ -454,7 +454,8 @@ fn compile(file: &str, lists: &[(&'static str, &[String])]) -> Result &'static SelectionPolicy { - static DEFAULTS: std::sync::LazyLock = std::sync::LazyLock::new(SelectionPolicy::unrestricted); + static DEFAULTS: std::sync::LazyLock = + std::sync::LazyLock::new(SelectionPolicy::unrestricted); &DEFAULTS } @@ -803,7 +804,9 @@ fn ceiling_dirs() -> Vec { #[cfg(unix)] fn trusted_owner(meta: &std::fs::Metadata) -> bool { use std::os::unix::fs::MetadataExt; - let sudo_uid = std::env::var("SUDO_UID").ok().and_then(|v| v.trim().parse::().ok()); + let sudo_uid = std::env::var("SUDO_UID") + .ok() + .and_then(|v| v.trim().parse::().ok()); // SAFETY: geteuid has no preconditions and cannot fail. owner_trusted(meta.uid(), unsafe { libc::geteuid() }, sudo_uid) } diff --git a/crates/socket-patch-core/src/policy/report.rs b/crates/socket-patch-core/src/policy/report.rs index ba8ff4221..0d095c7dc 100644 --- a/crates/socket-patch-core/src/policy/report.rs +++ b/crates/socket-patch-core/src/policy/report.rs @@ -48,7 +48,9 @@ pub fn policy_block( let (floor, floor_source) = policy.min_severity(); // Sorted: crawl order is filesystem order, and the two engines differ. let mut filtered: Vec<&FilteredEntry> = filtered.iter().collect(); - filtered.sort_by(|a, b| (&a.project, &a.purl, a.reason.code()).cmp(&(&b.project, &b.purl, b.reason.code()))); + filtered.sort_by(|a, b| { + (&a.project, &a.purl, a.reason.code()).cmp(&(&b.project, &b.purl, b.reason.code())) + }); let mut retained: Vec<&RetainedEntry> = retained.iter().collect(); retained.sort_by(|a, b| (&a.project, &a.purl).cmp(&(&b.project, &b.purl))); let filtered: Vec = filtered diff --git a/crates/socket-patch-core/src/policy/socket_yml.rs b/crates/socket-patch-core/src/policy/socket_yml.rs index 30a99499c..103fe20bd 100644 --- a/crates/socket-patch-core/src/policy/socket_yml.rs +++ b/crates/socket-patch-core/src/policy/socket_yml.rs @@ -486,7 +486,11 @@ pub(crate) fn package_spec_error(spec: &str) -> Option<&'static str> { if spec.is_empty() { return Some("package spec is empty"); } - if let Some(rest) = spec.get(..4).filter(|p| p.eq_ignore_ascii_case("pkg:")).map(|_| &spec[4..]) { + if let Some(rest) = spec + .get(..4) + .filter(|p| p.eq_ignore_ascii_case("pkg:")) + .map(|_| &spec[4..]) + { let valid = rest.split_once('/').is_some_and(|(ty, name)| { !ty.is_empty() && !name.trim_matches('/').is_empty() && !name.starts_with('@') }); @@ -785,7 +789,9 @@ pub(crate) fn parse_file( Some(Err((key, message))) => { warnings.push(PolicyWarning { code: super::SOCKET_YML_IGNORED_VALUE, - detail: super::strip_unsafe(&format!("{file}: {key} {message}; the key is ignored")), + detail: super::strip_unsafe(&format!( + "{file}: {key} {message}; the key is ignored" + )), }); Vec::new() } @@ -916,8 +922,12 @@ mod tests { // YAML beats everything; the case variant beats the version gate; // the version gate beats the keys. assert_eq!(err_key("patches: {minSeverty: x}\n").0, "version"); - assert!(err_key("Patches: {}\npatches: {minSeverty: x}\n").1.contains("misspelled")); - assert!(err_key("patches: {minSeverty: x\n").1.contains("invalid YAML")); + assert!(err_key("Patches: {}\npatches: {minSeverty: x}\n") + .1 + .contains("misspelled")); + assert!(err_key("patches: {minSeverty: x\n") + .1 + .contains("invalid YAML")); } #[test] @@ -986,12 +996,9 @@ mod tests { let (key, message) = err_key(text); assert_eq!(key, "", "{text:?}"); assert!( - [ - "invalid YAML", - "top level must be a mapping", - ] - .iter() - .any(|m| message.contains(m)), + ["invalid YAML", "top level must be a mapping",] + .iter() + .any(|m| message.contains(m)), "{text:?}: {message}" ); } diff --git a/crates/socket-patch-core/src/policy/tests.rs b/crates/socket-patch-core/src/policy/tests.rs index 9a6d247a2..f64635eba 100644 --- a/crates/socket-patch-core/src/policy/tests.rs +++ b/crates/socket-patch-core/src/policy/tests.rs @@ -418,8 +418,14 @@ fn composer_package_filters_match_release_identity_and_preserve_branch_case() { Err(FilterReason::PackageIgnored { .. }) )); assert!(policy.admits_purl("pkg:composer/psr/log@3.0.3").is_ok()); - assert!(package_spec_matches("pkg:composer/PSR/Log@3.0.2.0", "pkg:composer/psr/log@3.0.2")); - assert!(!package_spec_matches("pkg:composer/psr/log@dev-Feature", "pkg:composer/psr/log@dev-feature")); + assert!(package_spec_matches( + "pkg:composer/PSR/Log@3.0.2.0", + "pkg:composer/psr/log@3.0.2" + )); + assert!(!package_spec_matches( + "pkg:composer/psr/log@dev-Feature", + "pkg:composer/psr/log@dev-feature" + )); } #[test] @@ -577,7 +583,10 @@ mod disk { assert!(owner_trusted(1000, 1000, None)); assert!(owner_trusted(0, 1000, None)); assert!(!owner_trusted(1001, 1000, None)); - assert!(owner_trusted(1001, 1000, Some(1001)), "sudo's invoking user"); + assert!( + owner_trusted(1001, 1000, Some(1001)), + "sudo's invoking user" + ); assert!(owner_trusted(1001, 0, None), "root trusts every owner"); } @@ -598,13 +607,21 @@ mod disk { fn this_repos_socket_yml_loads_and_excludes_its_fixtures() { let repo = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); let (policy, warnings) = - SelectionPolicy::load(&DiskPolicyFs::new(&repo), &PolicyOverrides::default()).expect("valid"); + SelectionPolicy::load(&DiskPolicyFs::new(&repo), &PolicyOverrides::default()) + .expect("valid"); assert!(warnings.is_empty(), "{warnings:?}"); assert!(matches!(policy.source(), PolicySource::File { path, .. } if path == "socket.yml")); let lock = strings(&["package-lock.json"]); let err = policy - .admits_root(&root("crates/socket-patch-core/tests/fixtures/redirect/npm", &lock, true)) + .admits_root(&root( + "crates/socket-patch-core/tests/fixtures/redirect/npm", + &lock, + true, + )) .unwrap_err(); - assert_eq!(err.detail(), "crates/socket-patch-core/tests/fixtures/** (projectIgnorePaths)"); + assert_eq!( + err.detail(), + "crates/socket-patch-core/tests/fixtures/** (projectIgnorePaths)" + ); assert!(policy.admits_root(&root("", &lock, true)).is_ok()); } diff --git a/crates/socket-patch-core/src/rollout/stage.rs b/crates/socket-patch-core/src/rollout/stage.rs index 9ebd7e7ac..7c24ebadf 100644 --- a/crates/socket-patch-core/src/rollout/stage.rs +++ b/crates/socket-patch-core/src/rollout/stage.rs @@ -394,7 +394,11 @@ impl Stage { "a patch lookup failed for a package that could get its first patch, so \ no new patches were added this run ({} deferred) and none can take the \ missing package's place; re-run once the API answers", - if deferred == 1 { "1 package".to_string() } else { format!("{deferred} packages") } + if deferred == 1 { + "1 package".to_string() + } else { + format!("{deferred} packages") + } ), )); } @@ -415,7 +419,9 @@ impl Stage { purl: c.purl.clone(), uuid: c.uuid.clone(), reason: ROLLOUT_DEFERRED.to_string(), - detail: Some(format!("rank {rank} in the rollout queue; a later scan adds it")), + detail: Some(format!( + "rank {rank} in the rollout queue; a later scan adds it" + )), }) .collect() } @@ -502,4 +508,3 @@ pub fn rollout_json(configured: &MaxNew, plan: Option<&RolloutPlan>) -> serde_js "deferred": deferred, }) } - diff --git a/crates/socket-patch-core/src/telemetry.rs b/crates/socket-patch-core/src/telemetry.rs index d49aaa5c6..5f0eccb8d 100644 --- a/crates/socket-patch-core/src/telemetry.rs +++ b/crates/socket-patch-core/src/telemetry.rs @@ -4,9 +4,7 @@ use once_cell::sync::Lazy; use uuid::Uuid; use crate::constants::USER_AGENT; -use crate::utils::env_compat::{ - is_debug_enabled, is_offline_env, proxy_url_from_env, -}; +use crate::utils::env_compat::{is_debug_enabled, is_offline_env, proxy_url_from_env}; use crate::utils::fs::home_dir; use crate::vex::time::unix_to_ymdhms; diff --git a/crates/socket-patch-core/src/update/download.rs b/crates/socket-patch-core/src/update/download.rs index ea7339f63..fbee9ebc8 100644 --- a/crates/socket-patch-core/src/update/download.rs +++ b/crates/socket-patch-core/src/update/download.rs @@ -739,7 +739,10 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let missing = tmp.path().join("never-existed"); sweep_stale_stages(&missing); - assert!(!missing.exists(), "sweep must not create the destination dir"); + assert!( + !missing.exists(), + "sweep must not create the destination dir" + ); } /// A write failure AFTER a successful open (EFBIG here, standing in @@ -755,8 +758,7 @@ mod tests { #[test] fn stage_write_failure_cleans_up_stage_file() { const CHILD_ENV: &str = "SOCKET_PATCH_CORE_TEST_STAGE_FSIZE_CHILD"; - const TEST_NAME: &str = - "update::download::tests::stage_write_failure_cleans_up_stage_file"; + const TEST_NAME: &str = "update::download::tests::stage_write_failure_cleans_up_stage_file"; if std::env::var_os(CHILD_ENV).is_none() { let exe = std::env::current_exe().expect("test binary path must resolve"); let output = std::process::Command::new(exe) @@ -822,7 +824,10 @@ mod tests { matches!(err, UpdateError::SwapFailed(_)), "expected SwapFailed, got: {err}" ); - assert!(err.to_string().contains("error writing staged binary"), "{err}"); + assert!( + err.to_string().contains("error writing staged binary"), + "{err}" + ); let leftovers: Vec = std::fs::read_dir(tmp.path()) .unwrap() .map(|e| e.unwrap().file_name().to_string_lossy().into_owned()) diff --git a/crates/socket-patch-core/src/update/release.rs b/crates/socket-patch-core/src/update/release.rs index 5b2869012..7c3b04c29 100644 --- a/crates/socket-patch-core/src/update/release.rs +++ b/crates/socket-patch-core/src/update/release.rs @@ -751,9 +751,11 @@ mod tests { .mount(&server) .await; - let client = - metadata_client(&short_timeouts(), follow_redirect_policy(&default_endpoints())) - .unwrap(); + let client = metadata_client( + &short_timeouts(), + follow_redirect_policy(&default_endpoints()), + ) + .unwrap(); let err = client .get(format!("{}/start", server.uri())) .send() @@ -786,9 +788,11 @@ mod tests { .mount(&server) .await; - let client = - metadata_client(&short_timeouts(), follow_redirect_policy(&default_endpoints())) - .unwrap(); + let client = metadata_client( + &short_timeouts(), + follow_redirect_policy(&default_endpoints()), + ) + .unwrap(); let err = client .get(format!("{}/start", server.uri())) .send() @@ -864,7 +868,10 @@ mod tests { .unwrap_err(); assert!(matches!(err, UpdateError::CheckFailed(_)), "{err:?}"); let msg = err.to_string(); - assert!(msg.contains("expected a redirect to the latest tag"), "{msg}"); + assert!( + msg.contains("expected a redirect to the latest tag"), + "{msg}" + ); assert!(msg.contains("API fallback:"), "{msg}"); assert!(msg.contains("returned 500"), "{msg}"); } @@ -945,8 +952,14 @@ mod tests { #[test] fn url_host_keeps_explicit_ports() { - assert_eq!(url_host("http://127.0.0.1:9/x").as_deref(), Some("127.0.0.1:9")); - assert_eq!(url_host("https://github.com/a").as_deref(), Some("github.com")); + assert_eq!( + url_host("http://127.0.0.1:9/x").as_deref(), + Some("127.0.0.1:9") + ); + assert_eq!( + url_host("https://github.com/a").as_deref(), + Some("github.com") + ); assert_eq!(url_host("not a url"), None); } @@ -959,7 +972,9 @@ mod tests { // code stays `check_failed` (stable contract). let server = MockServer::start().await; Mock::given(method("GET")) - .and(path("/SocketDev/socket-patch/releases/download/v1.2.3/SHA256SUMS")) + .and(path( + "/SocketDev/socket-patch/releases/download/v1.2.3/SHA256SUMS", + )) .respond_with(ResponseTemplate::new(404)) .mount(&server) .await; @@ -992,7 +1007,9 @@ mod tests { // silently. let server = MockServer::start().await; Mock::given(method("GET")) - .and(path("/SocketDev/socket-patch/releases/download/v1.2.3/SHA256SUMS")) + .and(path( + "/SocketDev/socket-patch/releases/download/v1.2.3/SHA256SUMS", + )) .respond_with(ResponseTemplate::new(500)) .mount(&server) .await; diff --git a/crates/socket-patch-core/src/utils/group_commit.rs b/crates/socket-patch-core/src/utils/group_commit.rs index 7b93ada11..94a2c1118 100644 --- a/crates/socket-patch-core/src/utils/group_commit.rs +++ b/crates/socket-patch-core/src/utils/group_commit.rs @@ -304,9 +304,9 @@ where // write the lock edits beside the pre-run ledger. Put the caller's value // back before the unwind continues — the same value a caught-and- // continued caller holds. - if let Err(panic) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - edit(Arc::make_mut(value)) - })) { + if let Err(panic) = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| edit(Arc::make_mut(value)))) + { files.insert(key, captured(value)); drop(files); std::panic::resume_unwind(panic); @@ -1766,7 +1766,10 @@ mod tests { .unwrap(); remove_dir_after_commit(&dir).await; drop(dropped); - assert!(dir.join("config.toml").exists(), "an abandoned commit removes nothing"); + assert!( + dir.join("config.toml").exists(), + "an abandoned commit removes nothing" + ); let group = GroupCommit::begin(root); super::super::fs::remove_file(&dir.join("config.toml")) @@ -1775,7 +1778,10 @@ mod tests { remove_dir_after_commit(&dir).await; assert!(dir.join("config.toml").exists(), "captured, still on disk"); group.commit().await.unwrap(); - assert!(!dir.exists(), "the emptied directory is removed after the commit"); + assert!( + !dir.exists(), + "the emptied directory is removed after the commit" + ); std::fs::create_dir_all(&dir).unwrap(); std::fs::write(dir.join("config.toml"), b"[patch]\n").unwrap(); @@ -1787,7 +1793,10 @@ mod tests { remove_dir_after_commit(&dir).await; group.commit().await.unwrap(); assert!(!dir.join("config.toml").exists()); - assert!(dir.join("credentials.toml").exists(), "a non-empty directory is kept"); + assert!( + dir.join("credentials.toml").exists(), + "a non-empty directory is kept" + ); remove_dir_after_commit(&root.join("gone")).await; std::fs::remove_file(dir.join("credentials.toml")).unwrap(); diff --git a/crates/socket-patch-core/src/utils/line_endings.rs b/crates/socket-patch-core/src/utils/line_endings.rs index 889f6ad32..c6f257359 100644 --- a/crates/socket-patch-core/src/utils/line_endings.rs +++ b/crates/socket-patch-core/src/utils/line_endings.rs @@ -119,5 +119,4 @@ mod tests { assert_eq!(majority_terminator("a\r\nb\n"), "\n", "a tie is LF"); assert_eq!(majority_terminator("{}"), "\n", "no break: LF, not os.EOL"); } - } diff --git a/crates/socket-patch-core/src/utils/lock_fragments.rs b/crates/socket-patch-core/src/utils/lock_fragments.rs index c78ca2004..499aa8158 100644 --- a/crates/socket-patch-core/src/utils/lock_fragments.rs +++ b/crates/socket-patch-core/src/utils/lock_fragments.rs @@ -13,6 +13,13 @@ use toml_edit::Table; use crate::utils::line_endings::majority_terminator; +#[cfg(test)] +thread_local! { + /// Whole-lock renders this thread's rewrites took, each followed by a + /// full re-parse: what a hosted rewrite of N deps must not pay N times. + pub(crate) static RENDERS: std::cell::Cell = const { std::cell::Cell::new(0) }; +} + /// Takes `name`'s fragments of `text` from its (spanned) parse. pub(crate) type FragmentsIn = fn(&toml_edit::Document, &str, &str) -> Result, String>; @@ -212,6 +219,8 @@ pub(crate) fn finish<'a>( before: Result, String>, fragments_in: FragmentsIn, ) -> Result, String> { + #[cfg(test)] + RENDERS.with(|renders| renders.set(renders.get() + 1)); let before = before?; let rendered = crate::utils::python_lock::preserve_line_endings(text, rendered); let after_doc = toml_edit::Document::parse(rendered).map_err(|e| e.to_string())?; diff --git a/crates/socket-patch-core/src/utils/process.rs b/crates/socket-patch-core/src/utils/process.rs index c69552a73..733489194 100644 --- a/crates/socket-patch-core/src/utils/process.rs +++ b/crates/socket-patch-core/src/utils/process.rs @@ -89,9 +89,7 @@ pub(crate) fn resolve_app_alias_with( std::env::split_paths(&path) .filter(|dir| dir.is_absolute()) .map(|dir| dir.join(format!("{name}.exe"))) - .find(|candidate| { - std::fs::symlink_metadata(candidate).is_ok_and(|meta| !meta.is_dir()) - }) + .find(|candidate| std::fs::symlink_metadata(candidate).is_ok_and(|meta| !meta.is_dir())) } /// A plain file that cannot be executed (a stray `bun` data file on PATH) @@ -427,7 +425,11 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let safe = tmp.path().join("bin"); std::fs::create_dir_all(&safe).unwrap(); - let relative = [PathBuf::from("."), PathBuf::from(""), PathBuf::from("planted")]; + let relative = [ + PathBuf::from("."), + PathBuf::from(""), + PathBuf::from("planted"), + ]; let only_relative = std::env::join_paths(&relative).unwrap(); let var = |name: &str| (name == "PATH").then(|| only_relative.clone()); @@ -437,7 +439,10 @@ mod tests { let with_safe = std::env::join_paths(relative.iter().cloned().chain([safe.clone()])).unwrap(); let var = |name: &str| (name == "PATH").then(|| with_safe.clone()); - assert_eq!(resolve_app_alias_with("yarn", &var), Some(safe.join("yarn.exe"))); + assert_eq!( + resolve_app_alias_with("yarn", &var), + Some(safe.join("yarn.exe")) + ); } #[test] diff --git a/crates/socket-patch-core/src/utils/python_script.rs b/crates/socket-patch-core/src/utils/python_script.rs index df7d84223..72ffdcf0c 100644 --- a/crates/socket-patch-core/src/utils/python_script.rs +++ b/crates/socket-patch-core/src/utils/python_script.rs @@ -686,7 +686,12 @@ mod rendering_tests { "{direct}" ); assert!(uv_line.ends_with('}'), "{direct}"); - assert!(direct.starts_with("[project]\nname = \"p\"\ndependencies = [\"alpha==1.0.0\"]\n\n[tool]\n"), "{direct}"); + assert!( + direct.starts_with( + "[project]\nname = \"p\"\ndependencies = [\"alpha==1.0.0\"]\n\n[tool]\n" + ), + "{direct}" + ); assert_settled(&direct); let transitive = rewrite_project_metadata( diff --git a/crates/socket-patch-core/src/vendor/bun_lockb.rs b/crates/socket-patch-core/src/vendor/bun_lockb.rs index f349f94b6..10173ec37 100644 --- a/crates/socket-patch-core/src/vendor/bun_lockb.rs +++ b/crates/socket-patch-core/src/vendor/bun_lockb.rs @@ -1872,7 +1872,10 @@ mod tests { lock.set_package(package.id, &repin, &digest()).unwrap(); assert_eq!(lock.bytes().len(), first.len(), "{version}"); assert!( - !lock.bytes().windows(token.len()).any(|w| w == token.as_bytes()), + !lock + .bytes() + .windows(token.len()) + .any(|w| w == token.as_bytes()), "{version}: the superseded URL is gone" ); // A remote tarball keeps the registry record's inactive bytes; a @@ -1915,7 +1918,9 @@ mod tests { .set_package(1, ".socket/vendor/npm/x/minimist-1.2.2.tgz", &digest()) .unwrap(); let at = local.resolution_at(1); - assert!(local.data[at + 16..at + local.resolution_size].iter().all(|b| *b == 0)); + assert!(local.data[at + 16..at + local.resolution_size] + .iter() + .all(|b| *b == 0)); } #[test] diff --git a/crates/socket-patch-core/src/vendor/cargo_lock.rs b/crates/socket-patch-core/src/vendor/cargo_lock.rs index 7e50bccaf..73bdf8275 100644 --- a/crates/socket-patch-core/src/vendor/cargo_lock.rs +++ b/crates/socket-patch-core/src/vendor/cargo_lock.rs @@ -64,11 +64,9 @@ use std::sync::Arc; use toml_edit::{DocumentMut, Item, Table}; use super::cargo_tag; -use crate::formats::cargo::{ - locked_packages, metadata_checksum_key, parse_ref, LockedPackage, -}; use super::parse_memo::ParseMemo; use super::state::CargoLockOriginal; +use crate::formats::cargo::{locked_packages, metadata_checksum_key, parse_ref, LockedPackage}; use crate::utils::fs::{atomic_write_bytes_preserving_mode, read_regular_to_string}; /// Why a lock edit could not be performed. diff --git a/crates/socket-patch-core/src/vendor/gem.rs b/crates/socket-patch-core/src/vendor/gem.rs index a9e7d65ff..77613e080 100644 --- a/crates/socket-patch-core/src/vendor/gem.rs +++ b/crates/socket-patch-core/src/vendor/gem.rs @@ -1636,12 +1636,14 @@ fn rest_blocks_edit(rest: &str) -> Option { } // A `**opts` splat or hash literal is kept after `path:` (#847): a // source hidden in it makes bundler refuse the Gemfile loudly. - gemfile::source_option(rest).filter(|opt| !opt.dynamic).map(|opt| { - format!( - "the declaration already carries `{}` (revert any previous vendoring first)", - opt.spelling - ) - }) + gemfile::source_option(rest) + .filter(|opt| !opt.dynamic) + .map(|opt| { + format!( + "the declaration already carries `{}` (revert any previous vendoring first)", + opt.spelling + ) + }) } /// The quoted `path:` option value on a gem line's argument tail (only the diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/view.rs b/crates/socket-patch-core/src/vendor/lock_inventory/view.rs index acd48d721..29a446efc 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/view.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/view.rs @@ -15,10 +15,10 @@ use std::sync::Arc; use crate::constants::npm_family::{ BUN_LOCK, BUN_LOCKB, NPM_LOCKS, PNPM_LOCK, PNP_MARKERS, VLT_LOCK, }; -use crate::utils::fs::{read_regular_to_bytes, read_regular_to_string}; -use crate::vendor::npm_flavor::NpmLockFlavor; use crate::formats::pnpm::{sniff_lock_grammar, PnpmLockGrammar}; use crate::formats::yarn::{sniff_grammar, YarnLockGrammar, UNIDENTIFIED_DETAIL}; +use crate::utils::fs::{read_regular_to_bytes, read_regular_to_string}; +use crate::vendor::npm_flavor::NpmLockFlavor; use crate::vendor::VendorWarning; /// One in-memory file. diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs b/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs index 9ed45e49d..c3c21f8e9 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs @@ -7,12 +7,12 @@ use toml_edit::{DocumentMut, Item}; use crate::constants::npm_family::{BUN_LOCK, BUN_LOCKB, NPM_LOCKS, PNPM_LOCK}; use crate::formats::pnpm::PnpmLock; +use crate::formats::yarn::is_berry_lock; use crate::utils::digest::is_sri_pin; use crate::utils::fs::{read_regular_to_bytes, read_regular_to_string}; use crate::utils::python_lock::{ lock_artifact, lock_package_collection, package_artifacts, uv_source_location, }; -use crate::formats::yarn::is_berry_lock; use crate::vendor::bun_lock_text::{decode_json_string, split_name_spec}; use crate::vendor::bun_lockb::BunLockb; use crate::vendor::yarn_berry_lock::berry_field; diff --git a/crates/socket-patch-core/src/vendor/prestage.rs b/crates/socket-patch-core/src/vendor/prestage.rs index fcc149cc1..78f5d4c98 100644 --- a/crates/socket-patch-core/src/vendor/prestage.rs +++ b/crates/socket-patch-core/src/vendor/prestage.rs @@ -490,7 +490,10 @@ mod sweep_tests { for dir in &kept { assert!(v.join(dir).exists(), "{dir} kept"); } - assert!(!v.join("gem").exists(), "the levels only the tree kept alive are pruned"); + assert!( + !v.join("gem").exists(), + "the levels only the tree kept alive are pruned" + ); assert!(!v.join(format!("composer/{u}/psr/log@3.0.2")).exists()); assert!(v.join("state.json").exists()); assert_eq!(sweep_stale(root).await, 0, "idempotent"); diff --git a/crates/socket-patch-core/src/vendor/toml_surgery.rs b/crates/socket-patch-core/src/vendor/toml_surgery.rs index 0b1777008..4d151f613 100644 --- a/crates/socket-patch-core/src/vendor/toml_surgery.rs +++ b/crates/socket-patch-core/src/vendor/toml_surgery.rs @@ -519,7 +519,8 @@ mod tests { // CRLF, and a hand edit can leave a mixed-ending file, so the // removal helpers must never normalize: every byte outside the // removed segment survives verbatim. - let wired = "[project]\r\nname = \"x\"\r\n\n[tool.uv.sources]\nfoo = { path = \"w.whl\" }\n"; + let wired = + "[project]\r\nname = \"x\"\r\n\n[tool.uv.sources]\nfoo = { path = \"w.whl\" }\n"; let after = remove_exact_line(wired, "foo = { path = \"w.whl\" }").unwrap(); assert_eq!(after, "[project]\r\nname = \"x\"\r\n\n[tool.uv.sources]\n"); assert_eq!( diff --git a/crates/socket-patch-core/src/vex/discover/cargo.rs b/crates/socket-patch-core/src/vex/discover/cargo.rs index 86aab22de..4056ea30b 100644 --- a/crates/socket-patch-core/src/vex/discover/cargo.rs +++ b/crates/socket-patch-core/src/vex/discover/cargo.rs @@ -738,23 +738,22 @@ async fn vendored_from_patches( } let copy_tagged = matches!(tag, CopyTag::Tagged(_) | CopyTag::Unreadable); if let Lock::Parsed(lock) = lock { - let why = - match lock.vendored_in_use(name, version, &vref.uuid, copy_tagged) { - CopyClaim::Consumed => None, - CopyClaim::OtherTag(other) => Some(format!( - "{CARGO_LOCK} builds the copy tagged for patch {other} ({name} {})", - cargo_tag::tag_version(version, other) - )), - CopyClaim::UntaggedOverride => Some(format!( - "{CARGO_LOCK} builds an untagged {name} {version}, not the copy (which \ + let why = match lock.vendored_in_use(name, version, &vref.uuid, copy_tagged) { + CopyClaim::Consumed => None, + CopyClaim::OtherTag(other) => Some(format!( + "{CARGO_LOCK} builds the copy tagged for patch {other} ({name} {})", + cargo_tag::tag_version(version, other) + )), + CopyClaim::UntaggedOverride => Some(format!( + "{CARGO_LOCK} builds an untagged {name} {version}, not the copy (which \ cargo would lock as {}): another [patch] or path dependency overrides it", - cargo_tag::tag_version(version, &vref.uuid) - )), - CopyClaim::NotConsumed => Some(format!( - "{CARGO_LOCK} does not build {name}@{version} from it (an unused patch, \ + cargo_tag::tag_version(version, &vref.uuid) + )), + CopyClaim::NotConsumed => Some(format!( + "{CARGO_LOCK} does not build {name}@{version} from it (an unused patch, \ or the lock resolves it from a registry)" - )), - }; + )), + }; if let Some(why) = why { out.diag( DIAG_REF_INVALID, diff --git a/crates/socket-patch-core/src/vex/discover/maven.rs b/crates/socket-patch-core/src/vex/discover/maven.rs index c73e1f978..c880b5afa 100644 --- a/crates/socket-patch-core/src/vex/discover/maven.rs +++ b/crates/socket-patch-core/src/vex/discover/maven.rs @@ -88,15 +88,15 @@ use super::{ Discovery, PatchedRef, WiringMode, DIAG_LOCKFILE_UNPARSEABLE, DIAG_REF_INVALID, DIAG_REF_UNATTRIBUTABLE, }; +use crate::formats::maven::{ + is_maven_coordinate, is_maven_version_text, parse_pom, split_socket_version, Pom, PomDep, + PomRepo, +}; use crate::patch::redirect::{ local_repo_artifact_path, MVN_CHECKSUMS, MVN_CONFIG, TRUSTED_CHECKSUMS_ON, }; use crate::utils::digest::sha256_hex; use crate::vendor::lock_inventory::LockIntegrity; -use crate::formats::maven::{ - is_maven_coordinate, is_maven_version_text, parse_pom, split_socket_version, Pom, PomDep, - PomRepo, -}; use crate::vendor::maven_repo::{sha1_sidecar_matches, VENDOR_REPO_URL_PREFIX}; use crate::vendor::path::{sweep_vendor_dirs, VENDOR_DIR}; diff --git a/crates/socket-patch-core/src/vex/discover/nuget.rs b/crates/socket-patch-core/src/vex/discover/nuget.rs index d7f3cd95d..3f608233f 100644 --- a/crates/socket-patch-core/src/vex/discover/nuget.rs +++ b/crates/socket-patch-core/src/vex/discover/nuget.rs @@ -73,8 +73,8 @@ use super::{ Discovery, PatchedRef, UnlockedPin, WiringMode, DIAG_LOCKFILE_UNPARSEABLE, DIAG_REF_INVALID, DIAG_REF_UNATTRIBUTABLE, }; -use crate::vendor::lock_inventory::LockIntegrity; use crate::formats::nuget::{parse_config, NugetConfig}; +use crate::vendor::lock_inventory::LockIntegrity; use crate::vendor::nuget_config::{same_file, CONFIG_NAMES}; use crate::vendor::nuget_feed::{is_plain_nuget_token, nuget_lock_entries, nupkg_leaf}; use crate::vendor::path::VENDOR_DIR; diff --git a/crates/socket-patch-core/tests/covgap_api_blob_fetcher.rs b/crates/socket-patch-core/tests/covgap_api_blob_fetcher.rs index 041c323ad..ffe7b5943 100644 --- a/crates/socket-patch-core/tests/covgap_api_blob_fetcher.rs +++ b/crates/socket-patch-core/tests/covgap_api_blob_fetcher.rs @@ -569,5 +569,8 @@ async fn fetch_missing_blobs_mixed_outcomes_aggregate_and_format() { // End-to-end formatter exercise with a genuinely mixed result. let rendered = format_fetch_result(&result); assert!(rendered.contains("Downloaded 1 blob\n"), "{rendered}"); - assert!(rendered.contains("Failed to download 2 blobs"), "{rendered}"); + assert!( + rendered.contains("Failed to download 2 blobs"), + "{rendered}" + ); } diff --git a/crates/socket-patch-core/tests/covgap_crawlers_composer_crawler.rs b/crates/socket-patch-core/tests/covgap_crawlers_composer_crawler.rs index 3c4c872f5..997175812 100644 --- a/crates/socket-patch-core/tests/covgap_crawlers_composer_crawler.rs +++ b/crates/socket-patch-core/tests/covgap_crawlers_composer_crawler.rs @@ -99,7 +99,11 @@ async fn get_vendor_paths_global_nonexistent_composer_home_falls_back() { fn write_composer_shim(dir: &Path, echo_path: &Path) { use std::os::unix::fs::PermissionsExt; let shim = dir.join("composer"); - std::fs::write(&shim, format!("#!/bin/sh\necho '{}'\n", echo_path.display())).unwrap(); + std::fs::write( + &shim, + format!("#!/bin/sh\necho '{}'\n", echo_path.display()), + ) + .unwrap(); std::fs::set_permissions(&shim, std::fs::Permissions::from_mode(0o755)).unwrap(); } diff --git a/crates/socket-patch-core/tests/hosted_inventory.rs b/crates/socket-patch-core/tests/hosted_inventory.rs index 1bb3772d2..db1ecd6ae 100644 --- a/crates/socket-patch-core/tests/hosted_inventory.rs +++ b/crates/socket-patch-core/tests/hosted_inventory.rs @@ -51,9 +51,15 @@ async fn contradicted_hosted_lock_is_contested_not_absent() { assert!(!inv.is_empty(), "contested wiring is hosted state: {inv:?}"); let refusal = inv.contested_refusal().expect("a refusal"); assert!(refusal.contains("npm-shrinkwrap.json"), "{refusal}"); - assert!(refusal.contains("git checkout -- npm-shrinkwrap.json"), "{refusal}"); + assert!( + refusal.contains("git checkout -- npm-shrinkwrap.json"), + "{refusal}" + ); assert!(refusal.contains("patched_ref_unattributable"), "{refusal}"); - assert!(!refusal.contains(GRANT), "the grant token is not a patch: {refusal}"); + assert!( + !refusal.contains(GRANT), + "the grant token is not a patch: {refusal}" + ); } #[tokio::test] diff --git a/crates/socket-patch-core/tests/poetry_hosted.rs b/crates/socket-patch-core/tests/poetry_hosted.rs index bd3ca3c83..2d2e17d5d 100644 --- a/crates/socket-patch-core/tests/poetry_hosted.rs +++ b/crates/socket-patch-core/tests/poetry_hosted.rs @@ -1,6 +1,4 @@ -use socket_patch_core::patch::redirect::{ - rewrite_registry_redirect, DepOverride, Integrity, -}; +use socket_patch_core::patch::redirect::{rewrite_registry_redirect, DepOverride, Integrity}; use socket_patch_core::utils::poetry_lock::rewrite_poetry_lock; use std::collections::BTreeMap; @@ -63,7 +61,11 @@ fn native_lock_generations_redirect_idempotently() { let codes: Vec<&str> = result.warnings.iter().map(|w| w.code.as_str()).collect(); assert_eq!( codes, - if pre_1_4 { vec!["redirect_poetry_stale_install_risk"] } else { vec![] }, + if pre_1_4 { + vec!["redirect_poetry_stale_install_risk"] + } else { + vec![] + }, "{version}: {:?}", result.warnings ); @@ -92,12 +94,24 @@ fn hosted_shapes_match_each_lock_generations_installer() { ) .files["poetry.lock"] .clone(); - assert!(lock10.contains(&format!("url = \"{URL}#sha256={sha}&\"")), "{lock10}"); + assert!( + lock10.contains(&format!("url = \"{URL}#sha256={sha}&\"")), + "{lock10}" + ); assert!(lock10.contains("reference = \"\""), "{lock10}"); - assert!(lock10.contains(&format!("urllib3 = [{{ file = \"{WHEEL}\", hash = \"sha256:{sha}\" }}]")), "{lock10}"); + assert!( + lock10.contains(&format!( + "urllib3 = [{{ file = \"{WHEEL}\", hash = \"sha256:{sha}\" }}]" + )), + "{lock10}" + ); // Poetry >= 1.2 consuming this 1.0 lock verifies the package `files` // entry, so it is written too (1.0 ignores the extra key). - assert_eq!(lock10.matches(&format!("sha256:{sha}")).count(), 2, "{lock10}"); + assert_eq!( + lock10.matches(&format!("sha256:{sha}")).count(), + 2, + "{lock10}" + ); let doc: toml_edit::DocumentMut = lock10.parse().unwrap(); assert!(doc["package"][0]["files"].is_array(), "{lock10}"); @@ -124,7 +138,11 @@ fn hosted_shapes_match_each_lock_generations_installer() { &BTreeMap::from([("poetry.lock".to_string(), lock10_populated)]), &[patch()], ); - assert!(rerun.files.is_empty() && rerun.warnings.is_empty(), "{:?}", rerun.warnings); + assert!( + rerun.files.is_empty() && rerun.warnings.is_empty(), + "{:?}", + rerun.warnings + ); let lock11 = rewrite_registry_redirect( &BTreeMap::from([("poetry.lock".to_string(), original("1.2.2"))]), @@ -132,8 +150,15 @@ fn hosted_shapes_match_each_lock_generations_installer() { ) .files["poetry.lock"] .clone(); - assert_eq!(lock11.matches(&format!("sha256:{sha}")).count(), 2, "package files + metadata.files:\n{lock11}"); - assert!(lock11.contains(&format!("url = \"{URL}\"")), "no fragment on 1.1"); + assert_eq!( + lock11.matches(&format!("sha256:{sha}")).count(), + 2, + "package files + metadata.files:\n{lock11}" + ); + assert!( + lock11.contains(&format!("url = \"{URL}\"")), + "no fragment on 1.1" + ); assert!(!lock11.contains("reference"), "{lock11}"); let doc: toml_edit::DocumentMut = lock11.parse().unwrap(); assert!(doc["package"][0]["files"].is_array()); @@ -145,11 +170,19 @@ fn hosted_shapes_match_each_lock_generations_installer() { ) .files["poetry.lock"] .clone(); - assert_eq!(lock21.matches(&format!("sha256:{sha}")).count(), 1, "{lock21}"); + assert_eq!( + lock21.matches(&format!("sha256:{sha}")).count(), + 1, + "{lock21}" + ); assert!(!lock21.contains("reference")); let pristine: toml_edit::DocumentMut = original("2.4.3").parse().unwrap(); let doc: toml_edit::DocumentMut = lock21.parse().unwrap(); - assert_eq!(doc["metadata"].to_string(), pristine["metadata"].to_string(), "[metadata] untouched on 2.x"); + assert_eq!( + doc["metadata"].to_string(), + pristine["metadata"].to_string(), + "[metadata] untouched on 2.x" + ); } #[test] @@ -248,14 +281,21 @@ fn absent_entries_warn_once_and_missing_sha256_is_gated_once_per_dep() { let codes: Vec<&str> = result.warnings.iter().map(|w| w.code.as_str()).collect(); assert_eq!( codes, - vec!["redirect_poetry_entry_not_found", "redirect_poetry_entry_not_found"] + vec![ + "redirect_poetry_entry_not_found", + "redirect_poetry_entry_not_found" + ] ); let mut missing_hash = patch(); missing_hash.integrity.sha256 = None; let result = rewrite_registry_redirect(&files, &[missing_hash]); assert!(result.files.is_empty()); let codes: Vec<&str> = result.warnings.iter().map(|w| w.code.as_str()).collect(); - assert_eq!(codes, vec!["redirect_poetry_missing_sha256"], "gated once, not once per lock"); + assert_eq!( + codes, + vec!["redirect_poetry_missing_sha256"], + "gated once, not once per lock" + ); } /// A future Poetry that bumps the lock minor (2.2) is rewritten like 2.1 in @@ -278,11 +318,20 @@ fn rotated_grant_token_supersedes_the_prior_hosted_url() { let first = rewrite_registry_redirect(&files, &[patch()]); let mut rotated = patch(); rotated.token = "00000000-0000-4000-8000-000000000000".into(); - rotated.artifact_url = URL.replace("7e52b8b6-53f2-4dc8-860a-1ae7ebd8be0e", "00000000-0000-4000-8000-000000000000"); + rotated.artifact_url = URL.replace( + "7e52b8b6-53f2-4dc8-860a-1ae7ebd8be0e", + "00000000-0000-4000-8000-000000000000", + ); let second = rewrite_registry_redirect(&first.files, &[rotated.clone()]); assert!(second.warnings.is_empty(), "{:?}", second.warnings); let lock = &second.files["poetry.lock"]; assert!(lock.contains(&rotated.artifact_url) && !lock.contains(URL)); assert_eq!(second.edits.len(), 1); - assert!(second.edits[0].original.as_ref().unwrap().as_str().unwrap().contains(URL)); + assert!(second.edits[0] + .original + .as_ref() + .unwrap() + .as_str() + .unwrap() + .contains(URL)); } diff --git a/crates/socket-patch-core/tests/telemetry_helpers_e2e.rs b/crates/socket-patch-core/tests/telemetry_helpers_e2e.rs index 33c34297c..abde352bb 100644 --- a/crates/socket-patch-core/tests/telemetry_helpers_e2e.rs +++ b/crates/socket-patch-core/tests/telemetry_helpers_e2e.rs @@ -18,11 +18,7 @@ use socket_patch_core::telemetry::{is_telemetry_disabled, sanitize_error_message /// Every environment variable that can independently disable telemetry. /// Scrubbing the full set is what makes the per-var causation asserts honest. -const DISABLE_VARS: &[&str] = &[ - "SOCKET_TELEMETRY_DISABLED", - "VITEST", - "SOCKET_OFFLINE", -]; +const DISABLE_VARS: &[&str] = &["SOCKET_TELEMETRY_DISABLED", "VITEST", "SOCKET_OFFLINE"]; /// Run `f` with all telemetry-disabling vars removed, restoring the prior /// values afterward even if `f` panics (so one failing assert can't poison diff --git a/crates/socket-patch-core/tests/upstream_restore_golden.rs b/crates/socket-patch-core/tests/upstream_restore_golden.rs index 863f8dc99..33ec523f2 100644 --- a/crates/socket-patch-core/tests/upstream_restore_golden.rs +++ b/crates/socket-patch-core/tests/upstream_restore_golden.rs @@ -13,10 +13,12 @@ use std::fs; use std::path::{Path, PathBuf}; use serial_test::serial; -use socket_patch_core::patch::redirect::{rewrite_registry_redirect_with_pipenv_version, DepOverride}; use socket_patch_core::patch::redirect::upstream::{ restore_upstream, HostedPin, PinStatus, RestoreOptions, }; +use socket_patch_core::patch::redirect::{ + rewrite_registry_redirect_with_pipenv_version, DepOverride, +}; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -29,7 +31,10 @@ fn walk(dir: &Path) -> BTreeMap { if !dir.is_dir() { return out; } - for entry in walkdir::WalkDir::new(dir).into_iter().filter_map(Result::ok) { + for entry in walkdir::WalkDir::new(dir) + .into_iter() + .filter_map(Result::ok) + { if entry.file_type().is_file() { let rel = entry .path() @@ -45,16 +50,27 @@ fn walk(dir: &Path) -> BTreeMap { /// Tokens of `pattern` that `input` holds and `expected` does not: the /// upstream values the hosted rewrite replaced. -fn vanished(input: &BTreeMap, expected: &BTreeMap, re: &str) -> Vec { +fn vanished( + input: &BTreeMap, + expected: &BTreeMap, + re: &str, +) -> Vec { let re = regex::Regex::new(re).unwrap(); let all = |files: &BTreeMap| -> BTreeSet { files .values() - .flat_map(|t| re.captures_iter(t).map(|c| c[1].to_string()).collect::>()) + .flat_map(|t| { + re.captures_iter(t) + .map(|c| c[1].to_string()) + .collect::>() + }) .collect() }; let after = all(expected); - let mut out: Vec = all(input).into_iter().filter(|t| !after.contains(t)).collect(); + let mut out: Vec = all(input) + .into_iter() + .filter(|t| !after.contains(t)) + .collect(); out.sort(); out } @@ -80,10 +96,9 @@ fn load(flavor: &str) -> Vec { // `expected/` holds only the files the rewrite changed. let mut expected = input.clone(); expected.extend(walk(&dir.join("expected"))); - let overrides = serde_json::from_str( - &fs::read_to_string(dir.join("overrides.json")).unwrap(), - ) - .unwrap(); + let overrides = + serde_json::from_str(&fs::read_to_string(dir.join("overrides.json")).unwrap()) + .unwrap(); Case { dir, input, @@ -132,10 +147,23 @@ async fn run_case_with( (walk(tmp.path()), statuses) } -fn assert_round_trip(case: &Case, after: &BTreeMap, statuses: &[(String, PinStatus)]) { - assert!(!statuses.is_empty(), "{}: discovery found no hosted pin", case.dir.display()); +fn assert_round_trip( + case: &Case, + after: &BTreeMap, + statuses: &[(String, PinStatus)], +) { + assert!( + !statuses.is_empty(), + "{}: discovery found no hosted pin", + case.dir.display() + ); for (purl, status) in statuses { - assert_eq!(*status, PinStatus::Restored, "{}: {purl}", case.dir.display()); + assert_eq!( + *status, + PinStatus::Restored, + "{}: {purl}", + case.dir.display() + ); } for (rel, want) in &case.input { assert_eq!( @@ -145,8 +173,15 @@ fn assert_round_trip(case: &Case, after: &BTreeMap, statuses: &[ case.dir.display() ); } - let extra: Vec<&String> = after.keys().filter(|k| !case.input.contains_key(*k)).collect(); - assert!(extra.is_empty(), "{}: left behind {extra:?}", case.dir.display()); + let extra: Vec<&String> = after + .keys() + .filter(|k| !case.input.contains_key(*k)) + .collect(); + assert!( + extra.is_empty(), + "{}: left behind {extra:?}", + case.dir.display() + ); } /// Sets env vars for the guard's lifetime (tests using it are `#[serial]`). @@ -207,10 +242,9 @@ async fn npm_mock(case: &Case) -> MockServer { } Mock::given(method("GET")) .and(path(format!("/{}/{version}", name.replace('/', "%2f")))) - .respond_with( - ResponseTemplate::new(200) - .set_body_json(serde_json::json!({ "name": name, "version": version, "dist": dist })), - ) + .respond_with(ResponseTemplate::new(200).set_body_json( + serde_json::json!({ "name": name, "version": version, "dist": dist }), + )) .mount(&server) .await; } @@ -449,7 +483,12 @@ fn assert_refused( } other => panic!("{}: expected a refusal, got {other:?}", case.dir.display()), } - assert_eq!(after, &case.expected, "{}: a refused pin must change nothing", case.dir.display()); + assert_eq!( + after, + &case.expected, + "{}: a refused pin must change nothing", + case.dir.display() + ); } fn offline() -> RestoreOptions { @@ -541,7 +580,8 @@ fn transitive_lock() -> String { #[serial] async fn gem_edge_shapes_round_trip() { let gemfile = "source \"https://rubygems.org\"\n\ngem \"puma\"\n\ngroup :test do\n gem \"rails\", \"7.0.0\", require: false\nend\n"; - let crlf_gemfile = "source \"https://rubygems.org\"\r\n\r\ngem \"rails\", \"7.0.0\"\r\ngem \"puma\"\r\n"; + let crlf_gemfile = + "source \"https://rubygems.org\"\r\n\r\ngem \"rails\", \"7.0.0\"\r\ngem \"puma\"\r\n"; let two_sources_gemfile = "source \"https://rubygems.org\"\n\ngem \"rails\", \"7.0.0\"\nsource \"https://gems.example.com\" do\n gem \"private-gem\"\nend\n"; let two_sources_lock = "GEM\n remote: https://gems.example.com/\n specs:\n private-gem (1.0.0)\n\nGEM\n remote: https://rubygems.org/\n specs:\n rails (7.0.0)\n\nPLATFORMS\n ruby\n\nDEPENDENCIES\n private-gem!\n rails (= 7.0.0)\n\nCHECKSUMS\n private-gem (1.0.0) sha256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n rails (7.0.0) sha256=2222222222222222222222222222222222222222222222222222222222222222\n\nBUNDLED WITH\n 2.6.2\n"; // Provably transitive: the rewriter appended after a trailing blank @@ -569,12 +609,18 @@ async fn gem_edge_shapes_round_trip() { ), synthetic( "multiple-gem-sections", - &[("Gemfile", two_sources_gemfile), ("Gemfile.lock", two_sources_lock)], + &[ + ("Gemfile", two_sources_gemfile), + ("Gemfile.lock", two_sources_lock), + ], gem_override("rails", "7.0.0"), ), synthetic( "transitive-appended", - &[("Gemfile", transitive_gemfile), ("Gemfile.lock", &transitive)], + &[ + ("Gemfile", transitive_gemfile), + ("Gemfile.lock", &transitive), + ], gem_override("zeitwerk", "2.6.0"), ), ]; @@ -633,7 +679,10 @@ async fn gem_pre_checksums_states() { .replace(" rails (~> 7.0)\n", " rails (= 7.0.0)!\n"); mixed.expected.insert("Gemfile.lock".into(), converged); let (after, statuses) = run_case_with(&mixed, None, &offline()).await; - assert_eq!(statuses, vec![("pkg:gem/rails@7.0.0".to_string(), PinStatus::Restored)]); + assert_eq!( + statuses, + vec![("pkg:gem/rails@7.0.0".to_string(), PinStatus::Restored)] + ); assert_eq!(after["Gemfile.lock"], restored_lock); assert_eq!(after["Gemfile"], restored_gemfile); @@ -646,7 +695,10 @@ async fn gem_pre_checksums_states() { .replace(" rails (~> 7.0)\n", " rails (= 7.0.0)!\n"); mixed.expected.insert("Gemfile.lock".into(), merged); let (after, statuses) = run_case_with(&mixed, None, &offline()).await; - assert_eq!(statuses, vec![("pkg:gem/rails@7.0.0".to_string(), PinStatus::Restored)]); + assert_eq!( + statuses, + vec![("pkg:gem/rails@7.0.0".to_string(), PinStatus::Restored)] + ); assert_eq!(after["Gemfile.lock"], restored_lock); assert_eq!(after["Gemfile"], restored_gemfile); } @@ -676,7 +728,10 @@ async fn gem_transitive_append_round_trips_unless_unprovable() { case.expected.insert("Gemfile".into(), legacy); let (after, statuses) = gem_run(&case).await; assert_eq!(statuses[0].1, PinStatus::Restored); - assert_eq!(after["Gemfile"], format!("{gemfile}gem \"zeitwerk\", \"2.6.0\"\n")); + assert_eq!( + after["Gemfile"], + format!("{gemfile}gem \"zeitwerk\", \"2.6.0\"\n") + ); assert_eq!( after["Gemfile.lock"], lock.replace(" puma\n", " puma\n zeitwerk (= 2.6.0)\n") @@ -705,10 +760,19 @@ async fn gem_refusals_leave_everything_hosted() { // The upstream section is another registry's. let mut foreign = case.clone_with("foreign-upstream"); foreign.edit_both("Gemfile.lock", |t| { - t.replace("remote: https://rubygems.org/", "remote: https://gems.example.com/") + t.replace( + "remote: https://rubygems.org/", + "remote: https://gems.example.com/", + ) }); let (after, statuses) = gem_run(&foreign).await; - assert_refused(&foreign, &after, &statuses, "Gemfile.lock", "not rubygems.org"); + assert_refused( + &foreign, + &after, + &statuses, + "Gemfile.lock", + "not rubygems.org", + ); // Two upstream sections, neither singled out. let mut ambiguous = case.clone_with("ambiguous-upstream"); ambiguous.edit_both("Gemfile.lock", |t| { @@ -717,18 +781,38 @@ async fn gem_refusals_leave_everything_hosted() { "GEM\n remote: https://gems.example.com/\n specs:\n other (1.0.0)\n\nPLATFORMS", ) }); - ambiguous.edit_both("Gemfile", |t| t.replace("source \"https://rubygems.org\"\n", "")); - ambiguous.edit_both("Gemfile.lock", |t| t.replace("remote: https://rubygems.org/", "remote: https://mirror.example.com/")); + ambiguous.edit_both("Gemfile", |t| { + t.replace("source \"https://rubygems.org\"\n", "") + }); + ambiguous.edit_both("Gemfile.lock", |t| { + t.replace( + "remote: https://rubygems.org/", + "remote: https://mirror.example.com/", + ) + }); let (after, statuses) = gem_run(&ambiguous).await; - assert_refused(&ambiguous, &after, &statuses, "Gemfile.lock", "upstream GEM sections"); + assert_refused( + &ambiguous, + &after, + &statuses, + "Gemfile.lock", + "upstream GEM sections", + ); // The Gemfile block was hand-edited. let mut edited = case.clone_with("edited-block"); edited.expected.insert( "Gemfile".into(), - edited.expected["Gemfile"].replace(" gem \"rails\", \"7.0.0\"", " gem \"rails\", \"~> 7.0\""), + edited.expected["Gemfile"] + .replace(" gem \"rails\", \"7.0.0\"", " gem \"rails\", \"~> 7.0\""), ); let (after, statuses) = gem_run(&edited).await; - assert_refused(&edited, &after, &statuses, "Gemfile.lock", "shape other than the source block"); + assert_refused( + &edited, + &after, + &statuses, + "Gemfile.lock", + "shape other than the source block", + ); } // ── composer ──────────────────────────────────────────────────────────────── @@ -897,7 +981,11 @@ async fn composer_edge_shapes_round_trip() { &[("composer.lock", &escaped)], composer_override("acme/tool", "dev-main"), ), - synthetic("crlf", &[("composer.lock", &crlf)], composer_override("psr/log", "1.1.4")), + synthetic( + "crlf", + &[("composer.lock", &crlf)], + composer_override("psr/log", "1.1.4"), + ), ]; for case in &cases { let (after, statuses) = composer_run(case, |_| {}).await; @@ -916,20 +1004,42 @@ async fn composer_refusals_leave_everything_hosted() { // Packagist now serves another commit for the version. let (after, statuses) = composer_run(&case, |d| d["dist"]["reference"] = "feedface".into()).await; - assert_refused(&case, &after, &statuses, "composer.lock", "packagist now serves"); + assert_refused( + &case, + &after, + &statuses, + "composer.lock", + "packagist now serves", + ); // Packagist does not list the version. let (after, statuses) = composer_run(&case, |d| d["version"] = "0.0.1".into()).await; - assert_refused(&case, &after, &statuses, "composer.lock", "does not list version 1.1.4"); + assert_refused( + &case, + &after, + &statuses, + "composer.lock", + "does not list version 1.1.4", + ); // Offline. let (after, statuses) = run_case_with(&case, None, &offline()).await; assert_refused(&case, &after, &statuses, "composer.lock", "offline"); // Locked from another repository. let mut foreign = case.clone_with("foreign"); foreign.edit_both("composer.lock", |t| { - t.replacen("https://packagist.org/downloads/", "https://repo.example.com/downloads/", 1) + t.replacen( + "https://packagist.org/downloads/", + "https://repo.example.com/downloads/", + 1, + ) }); let (after, statuses) = composer_run(&foreign, |_| {}).await; - assert_refused(&foreign, &after, &statuses, "composer.lock", "not packagist"); + assert_refused( + &foreign, + &after, + &statuses, + "composer.lock", + "not packagist", + ); // No notification-url, and composer.json names custom repositories. let mut custom = case.clone_with("custom-repos"); custom.edit_both("composer.lock", |t| { @@ -946,7 +1056,13 @@ async fn composer_refusals_leave_everything_hosted() { ); } let (after, statuses) = composer_run(&custom, |_| {}).await; - assert_refused(&custom, &after, &statuses, "composer.lock", "custom repositories"); + assert_refused( + &custom, + &after, + &statuses, + "composer.lock", + "custom repositories", + ); } // ── PyPI ───────────────────────────────────────────────────────────────────── @@ -984,7 +1100,11 @@ fn urllib3_dep() -> DepOverride { /// PyPI's blake2b-bucketed file URLs, with real urllib3 1.26.18's buckets: the /// sdist sorts before the wheel by URL, the reverse of filename order. fn pypi_file_url(filename: &str) -> String { - let bucket = if filename.ends_with(".tar.gz") { "0c/39" } else { "b0/53" }; + let bucket = if filename.ends_with(".tar.gz") { + "0c/39" + } else { + "b0/53" + }; format!("https://files.pythonhosted.org/packages/{bucket}/{filename}") } @@ -997,8 +1117,18 @@ fn urllib3_release() -> Release<'static> { "urllib3", "1.26.18", vec![ - (URLLIB3_WHEEL, URLLIB3_WHEEL_SHA, 143835, "2023-10-17T17:46:21.184066Z"), - (URLLIB3_SDIST, URLLIB3_SDIST_SHA, 305687, "2023-10-17T17:46:24.000000Z"), + ( + URLLIB3_WHEEL, + URLLIB3_WHEEL_SHA, + 143835, + "2023-10-17T17:46:21.184066Z", + ), + ( + URLLIB3_SDIST, + URLLIB3_SDIST_SHA, + 305687, + "2023-10-17T17:46:24.000000Z", + ), ], ) } @@ -1033,7 +1163,10 @@ async fn pypi_mock(releases: &[Release<'_>]) -> (MockServer, EnvGuard) { } fn tree(files: &[(&str, String)]) -> BTreeMap { - files.iter().map(|(k, v)| (k.to_string(), v.clone())).collect() + files + .iter() + .map(|(k, v)| (k.to_string(), v.clone())) + .collect() } /// `input` as the real hosted rewriter leaves it. @@ -1080,14 +1213,24 @@ async fn assert_pypi_round_trip( pipenv: Option, ) { let rewritten = hosted(input, deps, pipenv); - assert_ne!(&rewritten, input, "{label}: the hosted rewrite changed nothing"); + assert_ne!( + &rewritten, input, + "{label}: the hosted rewrite changed nothing" + ); let (after, statuses) = restore_tree(&rewritten, &RestoreOptions::default()).await; - assert!(!statuses.is_empty(), "{label}: discovery found no hosted pin"); + assert!( + !statuses.is_empty(), + "{label}: discovery found no hosted pin" + ); for (purl, status) in &statuses { assert_eq!(*status, PinStatus::Restored, "{label}: {purl}"); } for (rel, want) in input { - assert_eq!(after.get(rel), Some(want), "{label}: {rel} did not round-trip"); + assert_eq!( + after.get(rel), + Some(want), + "{label}: {rel} did not round-trip" + ); } let extra: Vec<&String> = after.keys().filter(|k| !input.contains_key(*k)).collect(); assert!(extra.is_empty(), "{label}: left behind {extra:?}"); @@ -1110,13 +1253,20 @@ async fn pypi_refusal( PinStatus::Restored => None, }) .collect(); - assert!(!refusals.is_empty() && refusals.len() == statuses.len(), "{statuses:?}"); + assert!( + !refusals.is_empty() && refusals.len() == statuses.len(), + "{statuses:?}" + ); (refusals.join("\n"), rewritten, after) } fn fixture(rel: &str) -> String { - fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures").join(rel)) - .unwrap() + fs::read_to_string( + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures") + .join(rel), + ) + .unwrap() } #[tokio::test] @@ -1129,7 +1279,12 @@ async fn requirements_golden_restores_modulo_name_casing() { let (after, statuses) = run_case(&case).await; assert!(!statuses.is_empty()); for (purl, status) in &statuses { - assert_eq!(*status, PinStatus::Restored, "{}: {purl}", case.dir.display()); + assert_eq!( + *status, + PinStatus::Restored, + "{}: {purl}", + case.dir.display() + ); } assert_eq!( after["requirements.txt"].to_ascii_lowercase(), @@ -1149,7 +1304,12 @@ async fn uv_golden_without_a_registry_sibling_is_refused() { let (_server, _env) = pypi_mock(&[( "click", "8.1.7", - vec![("click-8.1.7-py3-none-any.whl", URLLIB3_WHEEL_SHA, 1, "2023-08-17T17:29:10Z")], + vec![( + "click-8.1.7-py3-none-any.whl", + URLLIB3_WHEEL_SHA, + 1, + "2023-08-17T17:29:10Z", + )], )]) .await; let mut ran = 0; @@ -1164,7 +1324,10 @@ async fn uv_golden_without_a_registry_sibling_is_refused() { case.dir.display() ); } - assert_eq!(after, case.expected, "a refused pin must leave the files untouched"); + assert_eq!( + after, case.expected, + "a refused pin must leave the files untouched" + ); ran += 1; } assert!(ran > 0); @@ -1271,9 +1434,14 @@ async fn pdm_static_urls_round_trip() { &format!("{{url = \"{}\"", pypi_file_url(URLLIB3_SDIST)), ); // PDM writes a static_urls entry's files in URL order (sdist first here). - let wheel_line = format!(" {{url = \"{}\", hash = \"sha256:{URLLIB3_WHEEL_SHA}\"}},\n", pypi_file_url(URLLIB3_WHEEL)); + let wheel_line = format!( + " {{url = \"{}\", hash = \"sha256:{URLLIB3_WHEEL_SHA}\"}},\n", + pypi_file_url(URLLIB3_WHEEL) + ); assert!(lock.contains(&wheel_line), "{lock}"); - let lock = lock.replacen(&wheel_line, "", 1).replacen("\n]\n", &format!("\n{wheel_line}]\n"), 1); + let lock = + lock.replacen(&wheel_line, "", 1) + .replacen("\n]\n", &format!("\n{wheel_line}]\n"), 1); assert!( lock.find(URLLIB3_SDIST).unwrap() < lock.find(URLLIB3_WHEEL).unwrap(), "{lock}" @@ -1287,12 +1455,17 @@ async fn pdm_static_urls_round_trip() { async fn pdm_narrowed_lock_with_platform_wheels_is_refused() { let wheel = "urllib3-1.26.18-cp311-cp311-manylinux_2_17_x86_64.whl"; let mut release = urllib3_release(); - release.2.push((wheel, URLLIB3_WHEEL_SHA, 1, "2023-10-17T17:46:21Z")); + release + .2 + .push((wheel, URLLIB3_WHEEL_SHA, 1, "2023-10-17T17:46:21Z")); let (_server, _env) = pypi_mock(&[release]).await; let input = tree(&[("pdm.lock", fixture("pdm-native/2.29.2.lock"))]); let (why, rewritten, after) = pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; - assert!(why.contains("not derivable") && why.contains("cross_platform"), "{why}"); + assert!( + why.contains("not derivable") && why.contains("cross_platform"), + "{why}" + ); assert!(why.contains("git checkout -- pdm.lock"), "{why}"); assert_eq!(after, rewritten); // A cross-platform lock records every file, whatever its tags. @@ -1352,7 +1525,9 @@ async fn pipfile_lock_fixture_and_every_category_round_trip() { assert_pypi_round_trip(&label, &input, &[urllib3_dep()], None).await; } // Pipenv 7.x–2017 writes `path` (and, before 2018, no `index`). - let old = text.replace(",\n \"index\": \"pypi\"", "").replace("\"index\": \"pypi\",\n ", ""); + let old = text + .replace(",\n \"index\": \"pypi\"", "") + .replace("\"index\": \"pypi\",\n ", ""); assert!(!old.contains("\"index\""), "{old}"); let input = tree(&[("Pipfile.lock", old), ("Pipfile", "[packages]\n".into())]); assert_pypi_round_trip("pipenv 2017", &input, &[urllib3_dep()], Some(11)).await; @@ -1386,7 +1561,9 @@ async fn pipfile_lock_real_pipenv_shapes_round_trip_their_index() { let pipfile = fixture(&format!("{dir}/Pipfile")); let pristine: serde_json::Value = serde_json::from_str(&lock).unwrap(); assert_eq!( - pristine["default"]["urllib3"].get("index").and_then(|v| v.as_str()), + pristine["default"]["urllib3"] + .get("index") + .and_then(|v| v.as_str()), index, "{dir}: fixture drifted from what Pipenv writes" ); @@ -1401,9 +1578,16 @@ async fn pipfile_lock_real_pipenv_shapes_round_trip_their_index() { let hosted_lock = hosted(&input, &[urllib3_dep()], major)["Pipfile.lock"].clone(); let entry: serde_json::Value = serde_json::from_str(&hosted_lock).unwrap(); let entry = &entry["default"]["urllib3"]; - assert!(entry.get("file").is_some() && entry.get("version").is_none(), "{label}: {entry}"); + assert!( + entry.get("file").is_some() && entry.get("version").is_none(), + "{label}: {entry}" + ); for key in ["index", "markers", "extras"] { - assert_eq!(entry.get(key), pristine["default"]["urllib3"].get(key), "{label}: {key}"); + assert_eq!( + entry.get(key), + pristine["default"]["urllib3"].get(key), + "{label}: {key}" + ); } assert_pypi_round_trip(&label, &input, &[urllib3_dep()], major).await; } @@ -1427,12 +1611,17 @@ async fn pipfile_lock_marker_excluded_relock_hybrid_restores_the_original() { ("Pipfile", fixture(&format!("{dir}/Pipfile"))), ]); let rewritten = hosted(&input, &[urllib3_dep()], Some(2026)); - let mut relocked: serde_json::Value = - serde_json::from_str(&rewritten["Pipfile.lock"]).unwrap(); + let mut relocked: serde_json::Value = serde_json::from_str(&rewritten["Pipfile.lock"]).unwrap(); let pristine: serde_json::Value = serde_json::from_str(&lock).unwrap(); let entry = relocked["default"]["urllib3"].as_object_mut().unwrap(); - assert!(entry.contains_key("file") && !entry.contains_key("index"), "{entry:?}"); - entry.insert("hashes".into(), pristine["default"]["urllib3"]["hashes"].clone()); + assert!( + entry.contains_key("file") && !entry.contains_key("index"), + "{entry:?}" + ); + entry.insert( + "hashes".into(), + pristine["default"]["urllib3"]["hashes"].clone(), + ); entry.insert("version".into(), serde_json::json!("==1.26.18")); relocked.sort_all_objects(); let hybrid = reindent4(&serde_json::to_string_pretty(&relocked).unwrap()) + "\n"; @@ -1443,7 +1632,10 @@ async fn pipfile_lock_marker_excluded_relock_hybrid_restores_the_original() { for (purl, status) in &statuses { assert_eq!(*status, PinStatus::Restored, "{purl}"); } - assert_eq!(after["Pipfile.lock"], lock, "the hybrid restores the pristine bytes"); + assert_eq!( + after["Pipfile.lock"], lock, + "the hybrid restores the pristine bytes" + ); } #[tokio::test] @@ -1461,7 +1653,10 @@ async fn pipfile_lock_refusals() { ..Default::default() }; let (why, rewritten, after) = pypi_refusal(&input, &[urllib3_dep()], &offline).await; - assert!(why.contains("offline") && why.contains("git checkout -- Pipfile.lock"), "{why}"); + assert!( + why.contains("offline") && why.contains("git checkout -- Pipfile.lock"), + "{why}" + ); assert_eq!(after, rewritten); // A mirror as the only source. let mirror = lock.replace("https://pypi.org/simple", "https://mirror.example/simple"); @@ -1514,7 +1709,10 @@ async fn requirements_hash_mode_ambiguity_is_refused() { let (_server, _env) = pypi_mock(&[urllib3_release()]).await; let input = tree(&[("requirements.txt", "urllib3==1.26.18\n".into())]); let (why, _, _) = pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; - assert!(why.contains("hash-checking mode") && why.contains("not derivable"), "{why}"); + assert!( + why.contains("hash-checking mode") && why.contains("not derivable"), + "{why}" + ); let input = tree(&[( "requirements.txt", "idna==3.4 --hash=sha256:aaaa\nsix==1.16.0\nurllib3==1.26.18\n".into(), @@ -1532,7 +1730,10 @@ async fn requirements_hash_mode_ambiguity_is_refused() { offline: true, ..Default::default() }; - let input = tree(&[("requirements.txt", "flask==2.0.1\nurllib3==1.26.18\n".into())]); + let input = tree(&[( + "requirements.txt", + "flask==2.0.1\nurllib3==1.26.18\n".into(), + )]); let rewritten = hosted(&input, &[urllib3_dep()], None); let (after, statuses) = restore_tree(&rewritten, &offline).await; assert_eq!(statuses[0].1, PinStatus::Restored); @@ -1540,7 +1741,9 @@ async fn requirements_hash_mode_ambiguity_is_refused() { // …and is refused in hash mode. let input = tree(&[( "requirements.txt", - format!("idna==3.4 --hash=sha256:aaaa\nurllib3==1.26.18 --hash=sha256:{URLLIB3_WHEEL_SHA}\n"), + format!( + "idna==3.4 --hash=sha256:aaaa\nurllib3==1.26.18 --hash=sha256:{URLLIB3_WHEEL_SHA}\n" + ), )]); let (why, _, _) = pypi_refusal(&input, &[urllib3_dep()], &offline).await; assert!(why.contains("offline"), "{why}"); @@ -1551,7 +1754,12 @@ async fn requirements_hash_mode_ambiguity_is_refused() { async fn a_refused_pin_leaves_the_other_pins_restored() { // PyPI knows urllib3 only: idna's hashes cannot be re-derived. let (_server, _env) = pypi_mock(&[urllib3_release()]).await; - let idna = pypi_dep("idna", "3.4", "idna-3.4-py3-none-any.whl", "44444444-4444-4444-4444-444444444444"); + let idna = pypi_dep( + "idna", + "3.4", + "idna-3.4-py3-none-any.whl", + "44444444-4444-4444-4444-444444444444", + ); let input = tree(&[( "requirements.txt", format!( @@ -1565,7 +1773,10 @@ async fn a_refused_pin_leaves_the_other_pins_restored() { assert!(matches!(status("pkg:pypi/idna@3.4"), PinStatus::Refused(why) if why.contains("404"))); let lines: Vec<&str> = after["requirements.txt"].lines().collect(); assert_eq!(lines[0], "six==1.16.0 --hash=sha256:aaaa"); - assert!(lines[1].starts_with("idna @ https://patch.socket.dev/"), "{lines:?}"); + assert!( + lines[1].starts_with("idna @ https://patch.socket.dev/"), + "{lines:?}" + ); assert_eq!(lines[2], input["requirements.txt"].lines().nth(2).unwrap()); } @@ -1644,7 +1855,13 @@ async fn uv_project_locks_round_trip() { ("uv.lock", lock.replace('\n', eol)), ("pyproject.toml", pyproject.replace('\n', eol)), ]); - assert_pypi_round_trip(&format!("uv direct {eol:?}"), &input, &[urllib3_dep()], None).await; + assert_pypi_round_trip( + &format!("uv direct {eol:?}"), + &input, + &[urllib3_dep()], + None, + ) + .await; } // A transitive dependency: the override the rewrite pins in the // pyproject and the lock's `[manifest]` both go again. @@ -1752,7 +1969,11 @@ wheels = [{{ url = \"{wheel_url}\", upload-time = 2023-10-17T17:46:21.184Z, size /// microseconds), with (`uv export`) or without (`uv pip compile`) an /// `index` on each registry package. fn uv_pylock(index: bool) -> String { - let index = if index { "index = \"https://pypi.org/simple\"\n" } else { "" }; + let index = if index { + "index = \"https://pypi.org/simple\"\n" + } else { + "" + }; format!( "# This file was autogenerated by uv via the following command:\n\ # uv pip compile --format pylock.toml req.in -o pylock.toml\n\ @@ -1780,8 +2001,13 @@ async fn pylock_without_index_round_trips() { assert!(!lock.contains("index")); for eol in ["\n", "\r\n"] { let input = tree(&[("pylock.toml", lock.replace('\n', eol))]); - assert_pypi_round_trip(&format!("pip compile {eol:?}"), &input, &[urllib3_dep()], None) - .await; + assert_pypi_round_trip( + &format!("pip compile {eol:?}"), + &input, + &[urllib3_dep()], + None, + ) + .await; } // A sibling whose files come from another host is not PyPI. let mirror = lock.replace( @@ -1789,9 +2015,11 @@ async fn pylock_without_index_round_trips() { "https://mirror.example.com/packages/21/ed/", ); let input = tree(&[("pylock.toml", mirror)]); - let (why, _, after) = - pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; - assert!(after["pylock.toml"].contains("patch.socket.dev"), "the pin stays wired"); + let (why, _, after) = pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; + assert!( + after["pylock.toml"].contains("patch.socket.dev"), + "the pin stays wired" + ); assert!(why.contains("not PyPI"), "{why}"); } @@ -1958,7 +2186,10 @@ async fn uv_refusals() { { let (_server, _env) = pypi_mock(&[urllib3_release()]).await; // No other entry shows how this uv joins specifier clauses. - let input = tree(&[("uv.lock", direct.clone()), ("pyproject.toml", pyproject.into())]); + let input = tree(&[ + ("uv.lock", direct.clone()), + ("pyproject.toml", pyproject.into()), + ]); let (why, rewritten, after) = pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; assert!(why.contains("multi-clause"), "{why}"); @@ -1996,7 +2227,12 @@ async fn uv_refusals() { } // A release with interpreter-specific wheels. let mut release = urllib3_release(); - release.2.push(("urllib3-1.26.18-cp311-cp311-win_amd64.whl", URLLIB3_WHEEL_SHA, 1, "2023-10-17T17:46:21Z")); + release.2.push(( + "urllib3-1.26.18-cp311-cp311-win_amd64.whl", + URLLIB3_WHEEL_SHA, + 1, + "2023-10-17T17:46:21Z", + )); let (_server, _env) = pypi_mock(&[release]).await; let input = tree(&[("uv.lock", direct)]); let (why, _, _) = pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; @@ -2050,7 +2286,10 @@ async fn vlt_goldens_round_trip() { // refused a package whose package-lock.json entry the rewrite still // pinned; with vlt-lock.json upstream that pin is not live wiring, so // discovery (rightly) reports no pin to restore there. - let not_invertible = ["sibling-package-lock-vlt-installed", "sibling-refused-in-vlt"]; + let not_invertible = [ + "sibling-package-lock-vlt-installed", + "sibling-refused-in-vlt", + ]; let mut ran = 0; for case in load("npm/vlt") { let name = case.dir.file_name().unwrap().to_string_lossy().into_owned(); @@ -2095,7 +2334,10 @@ async fn maven_config_merge_keeps_the_resolver_lines() { .find(|c| c.dir.ends_with("mvn-config-merge")) .unwrap(); let (after, statuses) = run_case(&case).await; - assert!(matches!(statuses[..], [(_, PinStatus::Restored)]), "{statuses:?}"); + assert!( + matches!(statuses[..], [(_, PinStatus::Restored)]), + "{statuses:?}" + ); for rel in ["pom.xml", ".mvn/checksums/checksums.sha256"] { assert_eq!(after.get(rel), case.input.get(rel), "{rel}"); } @@ -2116,7 +2358,9 @@ async fn nuget_mock(case: &Case) -> MockServer { let (id, version) = (id.to_lowercase(), entry["resolved"].as_str().unwrap()); let catalog = format!("{}/catalog0/data/{id}.{version}.json", server.uri()); Mock::given(method("GET")) - .and(path(format!("/v3/registration5-gz-semver2/{id}/{version}.json"))) + .and(path(format!( + "/v3/registration5-gz-semver2/{id}/{version}.json" + ))) .respond_with( ResponseTemplate::new(200) .set_body_json(serde_json::json!({ "catalogEntry": catalog })), @@ -2186,11 +2430,17 @@ async fn nuget_non_invertible_goldens_restore_or_refuse_as_documented() { let PinStatus::Refused(why) = status else { panic!("{name}: {status:?}"); }; - assert!(why.contains("corp-feed") && why.contains("git checkout"), "{why}"); + assert!( + why.contains("corp-feed") && why.contains("git checkout"), + "{why}" + ); assert_eq!(after, case.expected, "{name}: a refusal changes nothing"); } else { assert_eq!(*status, PinStatus::Restored, "{name}"); - assert_eq!(after.get("packages.lock.json"), case.input.get("packages.lock.json")); + assert_eq!( + after.get("packages.lock.json"), + case.input.get("packages.lock.json") + ); let config = &after["nuget.config"]; assert!(!config.contains("socket-patch") && !config.contains("packageSourceMapping")); } @@ -2228,5 +2478,8 @@ async fn yarn_classic_git_pattern_pin_is_refused() { }, other => panic!("one pin expected: {other:?}"), } - assert_eq!(fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(), lock); + assert_eq!( + fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(), + lock + ); } diff --git a/crates/socket-patch-core/tests/uv_hosted.rs b/crates/socket-patch-core/tests/uv_hosted.rs index 9a2526cd7..81696f5dc 100644 --- a/crates/socket-patch-core/tests/uv_hosted.rs +++ b/crates/socket-patch-core/tests/uv_hosted.rs @@ -1,8 +1,6 @@ use std::collections::BTreeMap; -use socket_patch_core::patch::redirect::{ - rewrite_registry_redirect, DepOverride, Integrity, -}; +use socket_patch_core::patch::redirect::{rewrite_registry_redirect, DepOverride, Integrity}; fn patch(name: &str) -> DepOverride { DepOverride { diff --git a/crates/socket-patch-node/src/lib.rs b/crates/socket-patch-node/src/lib.rs index d83403b84..29fa8e6ae 100644 --- a/crates/socket-patch-node/src/lib.rs +++ b/crates/socket-patch-node/src/lib.rs @@ -15,11 +15,11 @@ use std::sync::Arc; use napi::bindgen_prelude::{Buffer, External, Function, JsObjectValue, Object, PromiseRaw}; use napi::{Env, Status}; use napi_derive::napi; +use socket_patch_core::api::client::PatchApi; use socket_patch_core::hosted::memory::{ - self as hosted_memory, EngineError, HostedScanOptions, HostedScanOutput, PresentKind, SelectOptions, - SessionBuilder, TreeEntryInput, + self as hosted_memory, EngineError, HostedScanOptions, HostedScanOutput, PresentKind, + SelectOptions, SessionBuilder, TreeEntryInput, }; -use socket_patch_core::api::client::PatchApi; use tokio_util::sync::CancellationToken; use provider::{JsPatchApi, ProviderRefs}; From 134bea9e5a179418642a1d5da357e47f2818ed3e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 19:04:49 +0000 Subject: [PATCH 3/6] Rewrite each Poetry/PDM lock in one pass A hosted scan rewrote poetry.lock and pdm.lock once per patched package, and every rewrite rendered and re-parsed the whole lock. A project with a dozen patches paid for a dozen full parses, which made Poetry and PDM scans 3.5-4.5x slower per package than other managers. The shared lock-splice engine now plans every package against one parsed lock, applies all the changes, renders and re-parses once, and splices each package's changed fragments into the original text. The result is checked against the rendering byte for byte. When a lock mixes line endings, a package is rewritten twice, or any check fails, the rewrite falls back to the old package-by-package path, so output and recorded edits never change. Differential tests run both paths over every Poetry and PDM lock generation, LF, CRLF and mixed, with refusals, missing packages and re-runs mixed in, and require identical text and per-package results. Fixes #760, #762 Assisted-by: Claude Code:claude-opus-5-5 --- .../src/patch/redirect/pdm.rs | 143 +++++--- .../src/patch/redirect/poetry.rs | 90 +++-- .../src/utils/lock_fragments.rs | 160 ++++++++ .../socket-patch-core/src/utils/pdm_lock.rs | 281 +++++++++++++- .../src/utils/poetry_lock.rs | 344 ++++++++++++++++-- 5 files changed, 875 insertions(+), 143 deletions(-) diff --git a/crates/socket-patch-core/src/patch/redirect/pdm.rs b/crates/socket-patch-core/src/patch/redirect/pdm.rs index 80e0eb6b7..18f73bb76 100644 --- a/crates/socket-patch-core/src/patch/redirect/pdm.rs +++ b/crates/socket-patch-core/src/patch/redirect/pdm.rs @@ -4,7 +4,7 @@ use serde_json::json; use super::{DepOverride, FileEdit, RewriteResult, RewriteWarning}; use crate::crawlers::python_crawler::canonicalize_pypi_name; -use crate::utils::pdm_lock::{rewrite_pdm_lock_in, PdmLockParse}; +use crate::utils::pdm_lock::{rewrite_pdm_lock_all, LockBatch, LockStep, PdmLockDep, PdmLockParse}; pub(super) fn rewrite( files: &BTreeMap, @@ -14,28 +14,43 @@ pub(super) fn rewrite( let Some(original) = files.get("pdm.lock") else { return; }; - let mut text = original.clone(); - let mut stale_warned = false; - // Each lock state is parsed once: the presence probe, the rewrite, the - // format probe and the next dep all share it. + // Each dep's intake, in dep order: skipped (the lock lacks it), refused + // before the lock is read, or one dep of the batch rewrite. A package + // `pdm.lock` simply does not contain is not installed by pdm — a sibling + // `requirements.txt`/pylock may legitimately carry it — so we neither + // redirect it here nor veto the other pypi rewriters. Only a package the + // lock DOES contain but the plan refuses (source conflict, unsupported + // format, forked variants, bad hashes) withholds siblings. No rewrite + // adds or drops a package, so the probe reads the original lock. let mut parse = PdmLockParse::default(); - for dep in overrides.iter().filter(|dep| dep.ecosystem == "pypi") { - // A package `pdm.lock` simply does not contain is not installed by pdm — - // a sibling `requirements.txt`/pylock may legitimately carry it — so we - // neither redirect it here nor veto the other pypi rewriters. Only a - // package the lock DOES contain but the plan refuses (source conflict, - // unsupported format, forked variants, bad hashes) withholds siblings. - if !lock_contains(&mut parse, &text, &dep.name) { - continue; - } - match plan_in(&mut parse, &text, dep) { - Ok((rewritten, edits)) => { + let intake: Vec<(&DepOverride, Result)> = overrides + .iter() + .filter(|dep| dep.ecosystem == "pypi") + .filter(|dep| lock_contains(&mut parse, original, &dep.name)) + .map(|dep| (dep, artifact_of(dep))) + .collect(); + let lock_deps: Vec = intake + .iter() + .filter_map(|(dep, artifact)| Some(lock_dep(dep, artifact.as_ref().ok()?))) + .collect(); + // Every dep is rewritten over one parse and one render of the lock. + let LockBatch { text, steps } = rewrite_pdm_lock_all(original, &lock_deps); + let mut steps = steps.into_iter(); + // No rewrite touches `[metadata] lock_version`: read once. + let lock_ver: Option = parse.parsed(&text).ok().and_then(|lock| { + crate::utils::pdm_lock::lock_version(lock) + .ok() + .map(str::to_string) + }); + let mut stale_warned = false; + for (dep, intake) in intake { + let step = match intake { + Ok(_) => steps.next().expect("one step per batched dep"), + Err(detail) => LockStep::Refused(detail), + }; + match step { + LockStep::Rewritten(_) | LockStep::Unchanged => { result.confirmed_pdm_uuids.insert(dep.patch_uuid.clone()); - let lock_ver: Option = parse.parsed(&rewritten).ok().and_then(|lock| { - crate::utils::pdm_lock::lock_version(lock) - .ok() - .map(str::to_string) - }); if lock_ver.as_deref() == Some("2") { result.warnings.push(RewriteWarning { code: "redirect_pdm_legacy_sync_required".into(), detail: "PDM 0.x may regenerate freshly generated locks during install; use `pdm sync` to preserve this patch, or upgrade PDM".into() }); } @@ -65,10 +80,14 @@ pub(super) fn rewrite( ), }); } - text = rewritten; - result.edits.extend(edits); + if let LockStep::Rewritten(edits) = step { + result + .edits + .extend(edits.into_iter().map(|(old, new)| file_edit(dep, old, new))); + } } - Err(detail) => { + LockStep::NotFound => unreachable!("PDM refuses a package its lock lacks"), + LockStep::Refused(detail) => { result.refused_pdm_uuids.insert(dep.patch_uuid.clone()); result.warnings.push(RewriteWarning { code: "redirect_pdm_refused".into(), @@ -106,17 +125,11 @@ fn lock_contains(parse: &mut PdmLockParse, text: &str, name: &str) -> bool { } } -#[cfg(test)] -fn plan(text: &str, dep: &DepOverride) -> Result<(String, Vec), String> { - plan_in(&mut PdmLockParse::default(), text, dep) -} +/// A dep's wheel filename and SHA-256. +type Artifact<'a> = (String, &'a str); -/// Plan `dep`'s rewrite of `text`, reusing (and refreshing) `parse`. -fn plan_in( - parse: &mut PdmLockParse, - text: &str, - dep: &DepOverride, -) -> Result<(String, Vec), String> { +/// `dep`'s [`Artifact`], or its refusal before the lock is read. +fn artifact_of(dep: &DepOverride) -> Result, String> { let sha256 = dep .integrity .sha256 @@ -133,28 +146,48 @@ fn plan_in( .path_segments() .and_then(|mut segments| segments.next_back()) .ok_or("missing PDM wheel filename")?; - let rewrite = rewrite_pdm_lock_in( - parse, - text, - &dep.name, - &dep.version, - ("url", &dep.artifact_url), + Ok((filename.to_string(), sha256)) +} + +/// `dep`'s arguments to the lock rewrite, given its [`artifact_of`]. +fn lock_dep<'a>(dep: &'a DepOverride, (filename, sha256): &'a Artifact<'a>) -> PdmLockDep<'a> { + PdmLockDep { + name: &dep.name, + version: &dep.version, + source: ("url", &dep.artifact_url), filename, sha256, - )?; - let edits = rewrite - .edits()? - .into_iter() - .map(|(old, new)| FileEdit { - path: "pdm.lock".into(), - kind: "redirect_pdm_lock_package".into(), - action: "rewritten".into(), - key: Some(dep.name.clone()), - original: Some(json!(old)), - new: Some(json!(new)), - }) - .collect(); - Ok((rewrite.text, edits)) + } +} + +fn file_edit(dep: &DepOverride, old: String, new: String) -> FileEdit { + FileEdit { + path: "pdm.lock".into(), + kind: "redirect_pdm_lock_package".into(), + action: "rewritten".into(), + key: Some(dep.name.clone()), + original: Some(json!(old)), + new: Some(json!(new)), + } +} + +/// `dep`'s rewrite of `text` alone: the rewritten text and its edits. +#[cfg(test)] +fn plan(text: &str, dep: &DepOverride) -> Result<(String, Vec), String> { + let artifact = artifact_of(dep)?; + let LockBatch { text, mut steps } = rewrite_pdm_lock_all(text, &[lock_dep(dep, &artifact)]); + match steps.remove(0) { + LockStep::Rewritten(edits) => Ok(( + text, + edits + .into_iter() + .map(|(old, new)| file_edit(dep, old, new)) + .collect(), + )), + LockStep::Unchanged => Ok((text, Vec::new())), + LockStep::NotFound => unreachable!("PDM refuses a package its lock lacks"), + LockStep::Refused(detail) => Err(detail), + } } #[cfg(test)] @@ -491,6 +524,6 @@ mod parse_reuse_equivalence_tests { ); } } - assert!(checked >= 20, "only {checked} locks landed every dep"); + assert!(checked >= 16, "only {checked} locks landed every dep"); } } diff --git a/crates/socket-patch-core/src/patch/redirect/poetry.rs b/crates/socket-patch-core/src/patch/redirect/poetry.rs index 8a25c3807..5ee901ff6 100644 --- a/crates/socket-patch-core/src/patch/redirect/poetry.rs +++ b/crates/socket-patch-core/src/patch/redirect/poetry.rs @@ -9,7 +9,7 @@ use toml_edit::DocumentMut; use super::{DepOverride, FileEdit, RewriteResult, RewriteWarning}; use crate::utils::poetry_lock::{ - generated_by_version, lock_version, rewrite_poetry_lock_in, PoetryLockParse, + generated_by_version, lock_version, rewrite_poetry_lock_all, LockBatch, LockStep, PoetryLockDep, }; /// Whether the lock was written by a Poetry release older than 1.4. Those @@ -56,56 +56,42 @@ pub(super) fn rewrite_poetry( } } for (path, original) in locks { - let mut content = original.clone(); + let deps: Vec = usable + .iter() + .map(|&(dep, sha256)| PoetryLockDep { + name: &dep.name, + version: &dep.version, + source_type: "url", + source_url: &dep.artifact_url, + filename: dep.artifact_url.rsplit('/').next().unwrap_or(""), + sha256, + }) + .collect(); + // Every dep is rewritten over one parse and one render of the lock. + let LockBatch { + text: content, + steps, + } = rewrite_poetry_lock_all(original, &deps); let mut stale_warned = false; // `pre_1_4_writer` reads only the first line and `[metadata] - // lock-version`, which no package rewrite touches: judged once, on the - // lock as of its first rewrite (where it was always first judged). + // lock-version`, which no package rewrite touches: judged once. let mut writer_format: Option> = None; - // Each lock state is parsed once: a rewrite hands its parsed output - // to the next dep. - let mut parse = PoetryLockParse::default(); - for &(dep, sha256) in &usable { - let filename = dep.artifact_url.rsplit('/').next().unwrap_or(""); - match rewrite_poetry_lock_in( - &mut parse, - &content, - &dep.name, - &dep.version, - "url", - &dep.artifact_url, - filename, - sha256, - ) { - Ok(Some(rewrite)) if rewrite.text != content => { - match rewrite.edits() { - Ok(edits) => { - for (original, new) in edits { - result.edits.push(FileEdit { - path: path.clone(), - kind: "redirect_poetry_lock_package".into(), - action: "rewritten".into(), - key: Some(format!("{}@{}", dep.name, dep.version)), - original: Some(Value::String(original)), - new: Some(Value::String(new)), - }); - } - } - Err(detail) => { - result - .refused_python_lock_uuids - .insert(dep.patch_uuid.clone()); - result.warnings.push(RewriteWarning { - code: "redirect_poetry_lock_unsupported".into(), - detail: format!("{path}: {detail}"), - }); - continue; - } + for (&(dep, _), step) in usable.iter().zip(steps) { + match step { + LockStep::Rewritten(edits) => { + for (original, new) in edits { + result.edits.push(FileEdit { + path: path.clone(), + kind: "redirect_poetry_lock_package".into(), + action: "rewritten".into(), + key: Some(format!("{}@{}", dep.name, dep.version)), + original: Some(Value::String(original)), + new: Some(Value::String(new)), + }); } result .confirmed_python_lock_uuids .insert(dep.patch_uuid.clone()); - content = rewrite.text; if !stale_warned { if let Some(format) = *writer_format.get_or_insert_with(|| pre_1_4_writer(&content)) @@ -139,16 +125,16 @@ pub(super) fn rewrite_poetry( } } // Already redirected to this artifact (idempotent re-scan). - Ok(Some(_)) => { + LockStep::Unchanged => { result .confirmed_python_lock_uuids .insert(dep.patch_uuid.clone()); } - Ok(None) => result.warnings.push(RewriteWarning { + LockStep::NotFound => result.warnings.push(RewriteWarning { code: "redirect_poetry_entry_not_found".into(), detail: format!("no {path} entry for {}@{}", dep.name, dep.version), }), - Err(detail) => { + LockStep::Refused(detail) => { result .refused_python_lock_uuids .insert(dep.patch_uuid.clone()); @@ -295,6 +281,16 @@ mod equivalence_tests { for version in VERSIONS.iter().filter(|version| !version.starts_with("0.")) { for crlf in [false, true] { let mut lock = grown(version, 11); + // Give every clone its own populated legacy integrity entry, + // as Poetry writes them. + if let Some(start) = lock.find("\nurllib3 = [\n") { + let end = start + lock[start..].find("\n]").unwrap() + 2; + let entry = lock[start..end].to_string(); + let clones: String = (0..11) + .map(|i| entry.replacen("urllib3 =", &format!("pkg{i} ="), 1)) + .collect(); + lock.insert_str(end, &clones); + } if crlf { lock = lock.replace('\n', "\r\n"); } diff --git a/crates/socket-patch-core/src/utils/lock_fragments.rs b/crates/socket-patch-core/src/utils/lock_fragments.rs index 499aa8158..354b08baa 100644 --- a/crates/socket-patch-core/src/utils/lock_fragments.rs +++ b/crates/socket-patch-core/src/utils/lock_fragments.rs @@ -271,6 +271,166 @@ pub(crate) fn finish<'a>( }) } +/// What one dep's rewrite did to a lock, whether it ran in a +/// [`rewrite_batch`] or step by step. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LockStep { + /// The dep's unit changed: its `(original, replacement)` fragment edits. + Rewritten(Vec<(String, String)>), + /// The unit already carried this rewrite (an idempotent re-run). + Unchanged, + /// The lock has no unit for the dep (or locks another version). + NotFound, + /// The rewrite refused, leaving the lock as it was. + Refused(String), +} + +/// Every dep's [`LockStep`] over one lock, in dep order, and the lock text +/// after all of them. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LockBatch { + pub text: String, + pub steps: Vec, +} + +/// Every dep's rewrite of `text` over ONE parse and ONE render, with exactly +/// the outcome the step-by-step rewrite (one [`finish`] per dep, each parsing +/// the previous dep's output) would have had; `None` whenever this cannot +/// vouch for that, and the caller then goes step by step. +/// +/// `plan(i, lock)` settles dep `i` against the document as the previous deps +/// left it, so a refusal or not-found verdict is the one the step-by-step +/// rewrite would reach; `mutate` then applies a planned dep. Each rewritten +/// dep's fragments are taken from the original text and from the single +/// rendering, and spliced into the original text. That is only the +/// step-by-step result when the deps rewrite distinct packages (a second +/// rewrite of a package would start from the first one's output) and the +/// lock has one line-ending style (a mixed lock's majority, which spells +/// each spliced fragment, can shift as deps land), and the combined splice +/// must reproduce the rendering byte for byte. +pub(crate) fn rewrite_batch

( + text: &str, + names: &[&str], + mut plan: impl FnMut(usize, &Table) -> Result, String>, + mut mutate: impl FnMut(&mut toml_edit::DocumentMut, P) -> Result<(), String>, + fragments_in: FragmentsIn, +) -> Option { + if text.contains("\r\n") && text.replace("\r\n", "").contains('\n') { + return None; + } + let original = toml_edit::Document::parse(text.to_owned()).ok()?; + let mut lock = original.clone().into_mut(); + let mut steps = Vec::with_capacity(names.len()); + let mut rewritten: Vec<(usize, Vec)> = Vec::new(); + let mut packages = std::collections::BTreeSet::new(); + for (index, name) in names.iter().enumerate() { + match plan(index, lock.as_table()) { + Err(detail) => steps.push(LockStep::Refused(detail)), + Ok(None) => steps.push(LockStep::NotFound), + Ok(Some(planned)) => { + if !packages.insert(crate::crawlers::python_crawler::canonicalize_pypi_name( + name, + )) { + return None; + } + let before = fragments_in(&original, text, name).ok()?; + mutate(&mut lock, planned).ok()?; + rewritten.push((index, before)); + steps.push(LockStep::Unchanged); + } + } + } + if rewritten.is_empty() { + return Some(LockBatch { + text: text.to_string(), + steps, + }); + } + #[cfg(test)] + RENDERS.with(|renders| renders.set(renders.get() + 1)); + let rendered = crate::utils::python_lock::preserve_line_endings(text, lock.to_string()); + let after_doc = toml_edit::Document::parse(rendered).ok()?; + let rendered = after_doc.raw(); + let file_terminator = majority_terminator(text); + // Each changed fragment as the byte range of `text` it replaces (trimmed + // to what differs) and the replacement. + let mut splices: Vec<(std::ops::Range, &str)> = Vec::new(); + let mut edits_of: Vec<(usize, Vec<(String, String)>)> = Vec::new(); + for (index, before) in &rewritten { + let after = fragments_in(&after_doc, rendered, names[*index]).ok()?; + if before.len() != after.len() { + return None; + } + let mut edits = Vec::new(); + for (old, new) in before.iter().zip(after) { + let new = respell(old, &new, file_terminator); + if *old == new { + continue; + } + if text.matches(old.as_str()).count() != 1 { + return None; + } + edits.push((old.clone(), new)); + } + edits_of.push((*index, edits)); + } + for (_, edits) in &edits_of { + for (old, new) in edits { + let at = text.find(old.as_str())?; + // Only the bytes that differ are replaced, so the units' shared + // boundaries (a unit's fragment ends at the next unit's header) + // never overlap. + let common = + |a: &mut dyn Iterator| a.take_while(|(x, y)| x == y).count(); + let mut prefix = common(&mut old.bytes().zip(new.bytes())); + while !old.is_char_boundary(prefix) || !new.is_char_boundary(prefix) { + prefix -= 1; + } + let mut suffix = + common(&mut old[prefix..].bytes().rev().zip(new[prefix..].bytes().rev())); + while !old.is_char_boundary(old.len() - suffix) + || !new.is_char_boundary(new.len() - suffix) + { + suffix -= 1; + } + splices.push(( + at + prefix..at + old.len() - suffix, + &new[prefix..new.len() - suffix], + )); + } + } + splices.sort_by_key(|(range, _)| (range.start, range.end)); + let mut result = String::with_capacity(rendered.len()); + let mut copied = 0; + for (range, replacement) in &splices { + if range.start < copied { + return None; + } + result.push_str(&text[copied..range.start]); + result.push_str(replacement); + copied = range.end; + } + result.push_str(&text[copied..]); + if result != rendered { + return None; + } + for (index, edits) in edits_of { + if edits + .iter() + .any(|(_, new)| result.matches(new.as_str()).count() != 1) + { + return None; + } + if !edits.is_empty() { + steps[index] = LockStep::Rewritten(edits); + } + } + Some(LockBatch { + text: result, + steps, + }) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/socket-patch-core/src/utils/pdm_lock.rs b/crates/socket-patch-core/src/utils/pdm_lock.rs index dca8e88be..5149cd61f 100644 --- a/crates/socket-patch-core/src/utils/pdm_lock.rs +++ b/crates/socket-patch-core/src/utils/pdm_lock.rs @@ -4,8 +4,10 @@ use toml_edit::{value, Array, InlineTable, Item, Table, Value}; use crate::crawlers::python_crawler::canonicalize_pypi_name; use crate::utils::lock_fragments::{ - extend_span, finish, fragments_of, next_header_end, pair_fragments, FragmentRewrite, LockParse, + extend_span, finish, fragments_of, next_header_end, pair_fragments, rewrite_batch, + FragmentRewrite, LockParse, }; +pub use crate::utils::lock_fragments::{LockBatch, LockStep}; use crate::utils::python_lock::is_prior_hosted_url; pub fn lock_version(lock: &Table) -> Result<&str, String> { @@ -175,15 +177,7 @@ pub fn rewrite_pdm_lock_in<'a>( sha256: &str, ) -> Result, String> { let (kind, location) = source; - if !matches!(kind, "url" | "path") - || sha256.len() != 64 - || !sha256.bytes().all(|byte| byte.is_ascii_hexdigit()) - { - return Err("invalid PDM artifact source or SHA-256".into()); - } - if !crate::vendor::pypi_distribution::matches(filename, name, version) { - return Err("PDM patch wheel does not match package".into()); - } + check_pdm_artifact(name, version, kind, filename, sha256)?; let doc = parse.take(text, "PDM")?; let edits = match plan_pdm_rewrite(&doc, name, version, kind, location) { Ok(edits) => edits, @@ -197,6 +191,47 @@ pub fn rewrite_pdm_lock_in<'a>( // where a fresh parse would raise it, after the rewrite. let before = pdm_lock_fragments_in(&doc, text, name); let mut lock = doc.into_mut(); + mutate_pdm_lock(&mut lock, edits, source, filename, sha256)?; + finish( + "PDM", + parse, + text, + name, + lock.to_string(), + before, + pdm_lock_fragments_in::, + ) +} + +/// The rewrite's own refusals, settled before the lock is read. +fn check_pdm_artifact( + name: &str, + version: &str, + kind: &str, + filename: &str, + sha256: &str, +) -> Result<(), String> { + if !matches!(kind, "url" | "path") + || sha256.len() != 64 + || !sha256.bytes().all(|byte| byte.is_ascii_hexdigit()) + { + return Err("invalid PDM artifact source or SHA-256".into()); + } + if !crate::vendor::pypi_distribution::matches(filename, name, version) { + return Err("PDM patch wheel does not match package".into()); + } + Ok(()) +} + +/// Apply a planned rewrite (the units [`plan_pdm_rewrite`] settled) to the +/// parsed lock. +fn mutate_pdm_lock( + lock: &mut toml_edit::DocumentMut, + edits: Vec<(usize, bool, String)>, + (kind, location): (&str, &str), + filename: &str, + sha256: &str, +) -> Result<(), String> { for (index, inline_files, files_key) in edits { let mut file = InlineTable::new(); file.insert("file", Value::from(filename)); @@ -223,17 +258,90 @@ pub fn rewrite_pdm_lock_in<'a>( table.insert(&files_key, value(files)); } } - finish( - "PDM", - parse, + Ok(()) +} + +/// One dep of a [`rewrite_pdm_lock_all`]: the arguments of +/// [`rewrite_pdm_lock_in`] past the lock text. +pub struct PdmLockDep<'a> { + pub name: &'a str, + pub version: &'a str, + pub source: (&'a str, &'a str), + pub filename: &'a str, + pub sha256: &'a str, +} + +/// Every dep's [`rewrite_pdm_lock_in`] over `text`, each against the +/// previous one's output: one parse and one render of the lock for all of +/// them when the batch can vouch for the step-by-step result, else step by +/// step. A dep's step is never [`LockStep::NotFound`]: PDM refuses a +/// package its lock lacks. +pub fn rewrite_pdm_lock_all(text: &str, deps: &[PdmLockDep]) -> LockBatch { + rewrite_pdm_lock_batch(text, deps).unwrap_or_else(|| rewrite_pdm_lock_steps(text, deps)) +} + +/// [`rewrite_pdm_lock_all`] over one parse and one render, or `None` (see +/// [`rewrite_batch`]). +fn rewrite_pdm_lock_batch(text: &str, deps: &[PdmLockDep]) -> Option { + let names: Vec<&str> = deps.iter().map(|dep| dep.name).collect(); + rewrite_batch( text, - name, - lock.to_string(), - before, + &names, + |index, lock| { + let dep = &deps[index]; + check_pdm_artifact( + dep.name, + dep.version, + dep.source.0, + dep.filename, + dep.sha256, + )?; + let units = plan_pdm_rewrite(lock, dep.name, dep.version, dep.source.0, dep.source.1)?; + Ok(Some((units, index))) + }, + |lock, (units, index)| { + let dep = &deps[index]; + mutate_pdm_lock(lock, units, dep.source, dep.filename, dep.sha256) + }, pdm_lock_fragments_in::, ) } +/// [`rewrite_pdm_lock_all`] one dep at a time. +fn rewrite_pdm_lock_steps(text: &str, deps: &[PdmLockDep]) -> LockBatch { + let mut content = text.to_string(); + let mut parse = PdmLockParse::default(); + let mut steps = Vec::with_capacity(deps.len()); + for dep in deps { + let rewrite = rewrite_pdm_lock_in( + &mut parse, + &content, + dep.name, + dep.version, + dep.source, + dep.filename, + dep.sha256, + ); + let step = match rewrite.and_then(|rewrite| Ok((rewrite.edits()?, rewrite.text))) { + Ok((edits, rewritten)) => { + let step = if rewritten == content { + LockStep::Unchanged + } else { + LockStep::Rewritten(edits) + }; + content = rewritten; + step + } + Err(detail) => LockStep::Refused(detail), + }; + steps.push(step); + } + LockBatch { + text: content, + steps, + } +} + /// Every refusal of [`rewrite_pdm_lock_in`], read from the parsed lock before /// it mutates anything: the `(package index, inline files, legacy files key)` /// of each unit to rewrite. @@ -1056,3 +1164,144 @@ pub(crate) mod parse_reuse_tests { assert!(names(&mut parse, &first.text).contains(&"pkg1".to_string())); } } + +#[cfg(test)] +mod batch_equivalence_tests { + //! [`rewrite_pdm_lock_all`]'s one-render batch against the step-by-step + //! rewrite it replaces (#762): whenever the batch answers, its text and + //! every dep's step are the step-by-step ones. + use super::parse_reuse_tests::{fixtures, grown}; + use super::*; + + const SHA: &str = "34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"; + + struct Dep { + name: String, + version: &'static str, + kind: &'static str, + location: String, + sha256: String, + } + + fn dep(name: &str, version: &'static str, tag: &str) -> Dep { + Dep { + name: name.into(), + version, + kind: "url", + location: format!( + "https://patch.socket.dev/patch/pypi/{name}/{tag}/{name}-{version}-py3-none-any.whl" + ), + sha256: SHA.into(), + } + } + + fn lock_deps(deps: &[Dep]) -> Vec> { + deps.iter() + .map(|dep| PdmLockDep { + name: &dep.name, + version: dep.version, + source: (dep.kind, &dep.location), + filename: dep.location.rsplit('/').next().unwrap(), + sha256: &dep.sha256, + }) + .collect() + } + + /// The dep mixes run over each lock: every package (adjacent units), + /// every other one, reversed, interleaved with refusals, and a package + /// rewritten twice (which the batch hands back). + fn mixes(extra: usize) -> Vec<(Vec, bool)> { + let all = || { + std::iter::once(dep("urllib3", "1.26.18", "a")) + .chain((0..extra).map(|i| dep(&format!("pkg{i}"), "1.26.18", "a"))) + }; + let mut every_other: Vec = all().step_by(2).collect(); + every_other.push(dep("absent", "1.0.0", "a")); + let mut reversed: Vec = all().collect(); + reversed.reverse(); + let mut mixed: Vec = vec![dep("urllib3", "9.9.9", "a")]; + for (n, next) in all().enumerate() { + mixed.push(next); + if n == 1 { + mixed.push(Dep { + sha256: "not-a-sha".into(), + ..dep("pkg0", "1.26.18", "b") + }); + mixed.push(Dep { + kind: "path", + location: "./.socket/vendor/pypi/u/pkg0-1.26.18-py3-none-any.whl".into(), + ..dep("pkg0", "1.26.18", "a") + }); + } + } + mixed.push(dep("urllib3", "9.9.9", "a")); + let mut twice: Vec = all().collect(); + twice.push(dep("urllib3", "1.26.18", "rotated")); + vec![ + (all().collect(), true), + (every_other, true), + (reversed, true), + (mixed, true), + (twice, false), + ] + } + + #[test] + fn batch_matches_the_step_by_step_rewrite() { + let mut batched = 0; + let mut rendered = 0; + for (fixture, lock) in fixtures() { + for extra in [0, 1, 4] { + for style in ["lf", "crlf", "mixed"] { + let mut lock = grown(&lock.replace("\r\n", "\n"), extra); + match style { + "crlf" => lock = lock.replace('\n', "\r\n"), + "mixed" => lock = lock.replacen('\n', "\r\n", 1), + _ => {} + } + for (mix, (deps, batchable)) in mixes(extra).into_iter().enumerate() { + let deps = lock_deps(&deps); + let what = format!("{fixture} extra={extra} {style} mix={mix}"); + let first = rewrite_pdm_lock_steps(&lock, &deps); + let lands = first + .steps + .iter() + .any(|step| matches!(step, LockStep::Rewritten(_))); + // And again over the output: the idempotent re-scan. + for (rerun, text) in + [lock.clone(), first.text.clone()].into_iter().enumerate() + { + let steps = rewrite_pdm_lock_steps(&text, &deps); + let batch = rewrite_pdm_lock_batch(&text, &deps); + if let Some(batch) = &batch { + batched += 1; + rendered += usize::from(lands); + assert_eq!(batch, &steps, "{what}"); + } + assert_eq!(rewrite_pdm_lock_all(&text, &deps), steps, "{what}"); + if !lands { + continue; // an unsupported generation: all refused + } + if style == "mixed" { + // (The first rewrite may respell the lone CRLF + // line, leaving the re-run's lock all LF.) + assert!( + rerun == 1 || batch.is_none(), + "{what}: the batch must hand back" + ); + } else if batchable { + assert!(batch.is_some(), "{what}: the batch must answer"); + } else { + assert!(batch.is_none(), "{what}: the batch must hand back"); + } + } + } + } + } + } + assert!( + rendered > 200, + "only {rendered} landing cases batched ({batched})" + ); + } +} diff --git a/crates/socket-patch-core/src/utils/poetry_lock.rs b/crates/socket-patch-core/src/utils/poetry_lock.rs index 39f3e70f8..00b34db1e 100644 --- a/crates/socket-patch-core/src/utils/poetry_lock.rs +++ b/crates/socket-patch-core/src/utils/poetry_lock.rs @@ -16,8 +16,10 @@ use toml_edit::{value, Array, DocumentMut, InlineTable, Item, Table, TableLike, use crate::crawlers::python_crawler::canonicalize_pypi_name; use crate::utils::lock_fragments::{ - extend_span, finish, fragments_of, next_header_end, pair_fragments, FragmentRewrite, LockParse, + extend_span, finish, fragments_of, next_header_end, pair_fragments, rewrite_batch, + FragmentRewrite, LockParse, }; +pub use crate::utils::lock_fragments::{LockBatch, LockStep}; use crate::utils::python_lock::{is_prior_hosted_url, table_likes}; /// The `{file, hash}` tables Poetry records in `package`'s own @@ -250,18 +252,7 @@ pub fn rewrite_poetry_lock_in<'a>( filename: &str, sha256: &str, ) -> Result>, String> { - if !matches!(source_type, "file" | "url") - || sha256.len() != 64 - || !sha256.bytes().all(|byte| byte.is_ascii_hexdigit()) - { - return Err("invalid Poetry artifact source or SHA-256".into()); - } - // Poetry compares the lock's `sha256:` against `hashlib`'s lowercase - // hexdigest as strings, so an uppercase digest would fail every install. - let sha256 = sha256.to_ascii_lowercase(); - if !crate::vendor::pypi_distribution::matches(filename, name, version) { - return Err("Poetry patch wheel does not match the locked package".into()); - } + let sha256 = checked_sha256(name, version, source_type, filename, sha256)?; let doc = parse.take(text, "Poetry")?; let plan = match plan_poetry_rewrite(&doc, name, version, source_type, source_url, &sha256) { Ok(Some(plan)) => plan, @@ -271,16 +262,60 @@ pub fn rewrite_poetry_lock_in<'a>( return verdict.map(|_| None); } }; + // The original's fragments come from the same parse; an error surfaces + // where a fresh parse would raise it, after the rewrite. + let before = poetry_lock_fragments_in(&doc, text, name); + let mut lock = doc.into_mut(); + mutate_poetry_lock(&mut lock, plan, source_type, filename, &sha256)?; + finish( + "Poetry", + parse, + text, + name, + lock.to_string(), + before, + poetry_lock_fragments_in::, + ) + .map(Some) +} + +/// The rewrite's own refusals, settled before the lock is read: the +/// lowercase `sha256` to pin. +fn checked_sha256( + name: &str, + version: &str, + source_type: &str, + filename: &str, + sha256: &str, +) -> Result { + if !matches!(source_type, "file" | "url") + || sha256.len() != 64 + || !sha256.bytes().all(|byte| byte.is_ascii_hexdigit()) + { + return Err("invalid Poetry artifact source or SHA-256".into()); + } + if !crate::vendor::pypi_distribution::matches(filename, name, version) { + return Err("Poetry patch wheel does not match the locked package".into()); + } + // Poetry compares the lock's `sha256:` against `hashlib`'s lowercase + // hexdigest as strings, so an uppercase digest would fail every install. + Ok(sha256.to_ascii_lowercase()) +} + +/// Apply a planned rewrite to the parsed lock. +fn mutate_poetry_lock( + lock: &mut DocumentMut, + plan: PoetryLockPlan, + source_type: &str, + filename: &str, + sha256: &str, +) -> Result<(), String> { let PoetryLockPlan { format, effective_url, index, package_name, } = plan; - // The original's fragments come from the same parse; an error surfaces - // where a fresh parse would raise it, after the rewrite. - let before = poetry_lock_fragments_in(&doc, text, name); - let mut lock = doc.into_mut(); let package = lock .get_mut("package") .and_then(Item::as_array_of_tables_mut) @@ -325,23 +360,103 @@ pub fn rewrite_poetry_lock_in<'a>( .ok_or_else(|| format!("[metadata.{field}] is not a table"))?; if format == "0" { let mut hashes = Array::new(); - hashes.push(sha256.as_str()); + hashes.push(sha256); table.insert(&package_name, value(hashes)); } else { let entry = legacy_files_entry(table, &package_name, files, rewritten); table.insert(&package_name, entry); } } - finish( - "Poetry", - parse, + Ok(()) +} + +/// One dep of a [`rewrite_poetry_lock_all`]: the arguments of +/// [`rewrite_poetry_lock_in`] past the lock text. +pub struct PoetryLockDep<'a> { + pub name: &'a str, + pub version: &'a str, + pub source_type: &'a str, + pub source_url: &'a str, + pub filename: &'a str, + pub sha256: &'a str, +} + +/// Every dep's [`rewrite_poetry_lock_in`] over `text`, each against the +/// previous one's output: one parse and one render of the lock for all of +/// them when the batch can vouch for the step-by-step result, else step by +/// step. +pub fn rewrite_poetry_lock_all(text: &str, deps: &[PoetryLockDep]) -> LockBatch { + rewrite_poetry_lock_batch(text, deps).unwrap_or_else(|| rewrite_poetry_lock_steps(text, deps)) +} + +/// [`rewrite_poetry_lock_all`] over one parse and one render, or `None` (see +/// [`rewrite_batch`]). +fn rewrite_poetry_lock_batch(text: &str, deps: &[PoetryLockDep]) -> Option { + let names: Vec<&str> = deps.iter().map(|dep| dep.name).collect(); + rewrite_batch( text, - name, - lock.to_string(), - before, + &names, + |index, lock| { + let dep = &deps[index]; + let sha256 = checked_sha256( + dep.name, + dep.version, + dep.source_type, + dep.filename, + dep.sha256, + )?; + let plan = plan_poetry_rewrite( + lock, + dep.name, + dep.version, + dep.source_type, + dep.source_url, + &sha256, + )?; + Ok(plan.map(|plan| (plan, index, sha256))) + }, + |lock, (plan, index, sha256)| { + let dep = &deps[index]; + mutate_poetry_lock(lock, plan, dep.source_type, dep.filename, &sha256) + }, poetry_lock_fragments_in::, ) - .map(Some) +} + +/// [`rewrite_poetry_lock_all`] one dep at a time. +fn rewrite_poetry_lock_steps(text: &str, deps: &[PoetryLockDep]) -> LockBatch { + let mut content = text.to_string(); + let mut parse = PoetryLockParse::default(); + let mut steps = Vec::with_capacity(deps.len()); + for dep in deps { + let step = match rewrite_poetry_lock_in( + &mut parse, + &content, + dep.name, + dep.version, + dep.source_type, + dep.source_url, + dep.filename, + dep.sha256, + ) { + Ok(Some(rewrite)) if rewrite.text != content => match rewrite.edits() { + Ok(edits) => { + let text = rewrite.text; + content = text; + LockStep::Rewritten(edits) + } + Err(detail) => LockStep::Refused(detail), + }, + Ok(Some(_)) => LockStep::Unchanged, + Ok(None) => LockStep::NotFound, + Err(detail) => LockStep::Refused(detail), + }; + steps.push(step); + } + LockBatch { + text: content, + steps, + } } /// Every refusal and not-applicable verdict of [`rewrite_poetry_lock_in`] @@ -1082,3 +1197,182 @@ mod parse_reuse_equivalence_tests { assert!(matches!(got, Ok(None)), "the stale parse was reused"); } } + +#[cfg(test)] +mod batch_equivalence_tests { + //! [`rewrite_poetry_lock_all`]'s one-render batch against the + //! step-by-step rewrite it replaces (#760): whenever the batch answers, + //! its text and every dep's step are the step-by-step ones. + use super::*; + + const VERSIONS: &[&str] = &[ + "0.12.17", "1.0.10", "1.1.15", "1.2.2", "1.3.2", "1.4.2", "1.5.1", "1.6.1", "1.7.1", + "1.8.5", "2.0.1", "2.1.4", "2.2.1", "2.3.4", "2.4.3", + ]; + const SHA: &str = "34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"; + + /// The native fixture with `extra` clones of its urllib3 unit, adjacent + /// to it, each with its own legacy integrity entry. + fn grown(version: &str, extra: usize) -> String { + let lock = std::fs::read_to_string(format!( + "{}/tests/fixtures/poetry/{version}/poetry.lock", + env!("CARGO_MANIFEST_DIR") + )) + .unwrap() + .replace("\r\n", "\n"); + let meta = lock.find("\n[metadata]").unwrap(); + let first = lock.find("[[package]]").unwrap(); + let unit = &lock[first..meta]; + let mut out = lock[..meta].to_string(); + for i in 0..extra { + out.push('\n'); + out.push_str(&unit.replace("name = \"urllib3\"", &format!("name = \"pkg{i}\""))); + } + let mut tail = lock[meta..].to_string(); + for key in ["\nurllib3 = [\n", "\nurllib3 = []"] { + if let Some(start) = tail.find(key) { + let end = start + tail[start + 1..].find('\n').unwrap() + 1; + let end = if key.ends_with("[\n") { + start + tail[start..].find("\n]").unwrap() + 2 + } else { + end + }; + let entry = tail[start..end].to_string(); + let clones: String = (0..extra) + .map(|i| entry.replacen("urllib3 =", &format!("pkg{i} ="), 1)) + .collect(); + tail.insert_str(end, &clones); + break; + } + } + out + &tail + } + + struct Dep { + name: String, + version: &'static str, + source_type: &'static str, + url: String, + sha256: String, + } + + fn dep(name: &str, version: &'static str, tag: &str) -> Dep { + Dep { + name: name.into(), + version, + source_type: "url", + url: format!( + "https://patch.socket.dev/patch/pypi/{name}/{tag}/{name}-{version}-py2.py3-none-any.whl" + ), + sha256: SHA.into(), + } + } + + fn lock_deps(deps: &[Dep]) -> Vec> { + deps.iter() + .map(|dep| PoetryLockDep { + name: &dep.name, + version: dep.version, + source_type: dep.source_type, + source_url: &dep.url, + filename: dep.url.rsplit('/').next().unwrap(), + sha256: &dep.sha256, + }) + .collect() + } + + /// The dep mixes run over each lock: every package (adjacent units), + /// every other one, reversed, interleaved with refusals and not-found + /// verdicts, and a package rewritten twice (which the batch hands back). + fn mixes(extra: usize) -> Vec<(Vec, bool)> { + let all = || { + std::iter::once(dep("urllib3", "1.26.18", "a")) + .chain((0..extra).map(|i| dep(&format!("pkg{i}"), "1.26.18", "a"))) + }; + let mut every_other: Vec = all().step_by(2).collect(); + every_other.push(dep("absent", "1.0.0", "a")); + let mut reversed: Vec = all().collect(); + reversed.reverse(); + let mut mixed: Vec = vec![dep("urllib3", "9.9.9", "a")]; + for (n, dep) in all().enumerate() { + mixed.push(dep); + if n == 1 { + mixed.push(Dep { + sha256: "not-a-sha".into(), + ..super::batch_equivalence_tests::dep("pkg0", "1.26.18", "b") + }); + mixed.push(Dep { + source_type: "file", + url: ".socket/vendor/x/pkg0-1.26.18-py2.py3-none-any.whl".into(), + ..super::batch_equivalence_tests::dep("pkg0", "1.26.18", "a") + }); + } + } + mixed.push(dep("urllib3", "9.9.9", "a")); + let mut twice: Vec = all().collect(); + twice.push(dep("urllib3", "1.26.18", "rotated")); + vec![ + (all().collect(), true), + (every_other, true), + (reversed, true), + (mixed, true), + (twice, false), + ] + } + + #[test] + fn batch_matches_the_step_by_step_rewrite() { + let mut batched = 0; + let mut cases = 0; + for version in VERSIONS { + for extra in [0, 1, 4] { + for style in ["lf", "crlf", "mixed"] { + let mut lock = grown(version, extra); + match style { + "crlf" => lock = lock.replace('\n', "\r\n"), + "mixed" => lock = lock.replacen('\n', "\r\n", 1), + _ => {} + } + for (mix, (deps, batchable)) in mixes(extra).into_iter().enumerate() { + let deps = lock_deps(&deps); + let what = format!("{version} extra={extra} {style} mix={mix}"); + let steps = rewrite_poetry_lock_steps(&lock, &deps); + // And again over the output: the idempotent re-scan. + for (rerun, text) in + [lock.clone(), steps.text.clone()].into_iter().enumerate() + { + cases += 1; + let steps = rewrite_poetry_lock_steps(&text, &deps); + let batch = rewrite_poetry_lock_batch(&text, &deps); + if let Some(batch) = &batch { + batched += 1; + assert_eq!(batch, &steps, "{what}"); + } + assert_eq!(rewrite_poetry_lock_all(&text, &deps), steps, "{what}"); + // Poetry 0.12 refuses every URL source: nothing to + // render, so the batch answers. + if style == "mixed" { + // (The first rewrite may respell the lone CRLF + // line, leaving the re-run's lock all LF.) + assert!( + rerun == 1 || batch.is_none(), + "{what}: the batch must hand back" + ); + } else if version.starts_with("0.") { + assert!(batch.is_some(), "{what}: nothing rewritten"); + } else if !batchable { + assert!(batch.is_none(), "{what}: the batch must hand back"); + } else { + assert!(batch.is_some(), "{what}: the batch must answer"); + } + } + } + } + } + } + assert!( + batched * 2 > cases, + "only {batched} of {cases} cases batched" + ); + } +} From e40eed89edf589d27a5ffc04a773d76bc462c85f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 19:14:13 +0000 Subject: [PATCH 4/6] Read PDM lock_version from the parsed original The rewrite never changes [metadata] lock_version, so read it from the parse the presence probe already took instead of parsing the output. Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-core/src/patch/redirect/pdm.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/socket-patch-core/src/patch/redirect/pdm.rs b/crates/socket-patch-core/src/patch/redirect/pdm.rs index 18f73bb76..3cb7054c4 100644 --- a/crates/socket-patch-core/src/patch/redirect/pdm.rs +++ b/crates/socket-patch-core/src/patch/redirect/pdm.rs @@ -36,8 +36,9 @@ pub(super) fn rewrite( // Every dep is rewritten over one parse and one render of the lock. let LockBatch { text, steps } = rewrite_pdm_lock_all(original, &lock_deps); let mut steps = steps.into_iter(); - // No rewrite touches `[metadata] lock_version`: read once. - let lock_ver: Option = parse.parsed(&text).ok().and_then(|lock| { + // No rewrite touches `[metadata] lock_version`: read once, from the + // parse the presence probe already took. + let lock_ver: Option = parse.parsed(original).ok().and_then(|lock| { crate::utils::pdm_lock::lock_version(lock) .ok() .map(str::to_string) From c25dd8efef7e357ca18926cf9e0eb4f7cd821429 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 18:24:21 +0000 Subject: [PATCH 5/6] Route Gradle digests through utils::digest main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-core/src/crawlers/gradle_cache.rs | 9 ++++----- crates/socket-patch-core/src/patch/jvm_jar.rs | 7 ++----- crates/socket-patch-core/src/patch/sidecars/maven.rs | 4 +--- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/socket-patch-core/src/crawlers/gradle_cache.rs b/crates/socket-patch-core/src/crawlers/gradle_cache.rs index ef295ee27..afd7c4fba 100644 --- a/crates/socket-patch-core/src/crawlers/gradle_cache.rs +++ b/crates/socket-patch-core/src/crawlers/gradle_cache.rs @@ -70,8 +70,7 @@ pub fn hash_eq(dir_name: &str, sha1_hex: &str) -> bool { /// Whether `bytes` are the pristine download Gradle stored in the hash /// directory `dir_name` (their sha1 names it). pub fn pristine(dir_name: &str, bytes: &[u8]) -> bool { - use sha1::{Digest, Sha1}; - hash_eq(dir_name, &hex::encode(Sha1::digest(bytes))) + hash_eq(dir_name, &crate::utils::digest::sha1_hex_of(bytes)) } /// Whether `path` is a version directory of a `files-2.1` tree @@ -432,8 +431,6 @@ impl DerivedIndex { /// The [`DerivedCopies`] of the jar `jar_leaf` whose pristine bytes /// hash to `pristine_sha1`. pub fn query(&self, jar_leaf: &str, pristine_sha1: &str) -> DerivedCopies { - use sha1::{Digest, Sha1}; - let instrumented = format!("instrumented-{jar_leaf}"); let mut out = DerivedCopies { incomplete: self.incomplete, @@ -460,7 +457,9 @@ impl DerivedIndex { out.stale.push(path.clone()); } else if name == jar_leaf || name == instrumented { match crate::utils::fs::read_regular_to_bytes_sync(path) { - Ok(bytes) if hash_eq(&hex::encode(Sha1::digest(&bytes)), pristine_sha1) => { + Ok(bytes) + if hash_eq(&crate::utils::digest::sha1_hex_of(&bytes), pristine_sha1) => + { out.stale.push(path.clone()) } Ok(_) => out.unknown.push(path.clone()), diff --git a/crates/socket-patch-core/src/patch/jvm_jar.rs b/crates/socket-patch-core/src/patch/jvm_jar.rs index 82d679406..f38a84403 100644 --- a/crates/socket-patch-core/src/patch/jvm_jar.rs +++ b/crates/socket-patch-core/src/patch/jvm_jar.rs @@ -25,8 +25,6 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; -use sha1::Digest as _; - use crate::crawlers::gradle_cache; use crate::hash::git_sha256::compute_git_sha256_from_bytes; use crate::manifest::schema::PatchFileInfo; @@ -353,12 +351,11 @@ fn unpatched_members( } fn sha256_hex(bytes: &[u8]) -> String { - use sha2::Digest as _; - hex::encode(sha2::Sha256::digest(bytes)) + crate::utils::digest::sha256_hex_of(bytes) } fn sha1_hex(bytes: &[u8]) -> String { - hex::encode(sha1::Sha1::digest(bytes)) + crate::utils::digest::sha1_hex_of(bytes) } /// `/jvm-originals/.jar`. diff --git a/crates/socket-patch-core/src/patch/sidecars/maven.rs b/crates/socket-patch-core/src/patch/sidecars/maven.rs index f2f5a2466..8798bfce6 100644 --- a/crates/socket-patch-core/src/patch/sidecars/maven.rs +++ b/crates/socket-patch-core/src/patch/sidecars/maven.rs @@ -17,8 +17,6 @@ use std::path::{Path, PathBuf}; -use sha1::Digest as _; - use super::{ SidecarAdvisory, SidecarAdvisoryCode, SidecarError, SidecarFile, SidecarFileAction, SidecarPayload, SidecarSeverity, @@ -44,7 +42,7 @@ impl Algo { fn digest(self, bytes: &[u8]) -> String { match self { - Algo::Sha1 => hex::encode(sha1::Sha1::digest(bytes)), + Algo::Sha1 => crate::utils::digest::sha1_hex_of(bytes), Algo::Md5 => hex::encode(md5(bytes)), } } From 6e455922d26e7dd07062e863a6a499ef20936f01 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 20:44:49 +0000 Subject: [PATCH 6/6] Drop the unrelated formatting sweep Running cargo fmt over the whole workspace reformatted 117 files this PR does not otherwise touch, because main is not rustfmt-clean and CI does not check formatting. Restore those files to main and keep the diff to the Poetry/PDM rewrite and the ported digest fix. Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-cli/src/commands/list.rs | 15 +- crates/socket-patch-cli/src/commands/mod.rs | 22 +- .../src/commands/scan/discovery.rs | 62 +-- .../src/commands/scan/hosted.rs | 54 +- .../src/commands/scan/policy.rs | 68 +-- .../src/commands/scan/rollout.rs | 20 +- .../src/commands/scan/rollout_args.rs | 2 + .../socket-patch-cli/src/commands/vendor.rs | 5 +- .../tests/apply/apply_network.rs | 10 +- .../apply/in_process_gem_config_warning.rs | 4 +- .../tests/cli/covgap_output.rs | 10 +- .../tests/cli/interactive_prompts_e2e.rs | 5 +- .../tests/cli_config_fallback.rs | 7 +- .../socket-patch-cli/tests/cli_get_silent.rs | 5 +- .../socket-patch-cli/tests/cli_parse_list.rs | 11 +- .../tests/cli_parse_rollback.rs | 6 +- .../socket-patch-cli/tests/cli_parse_scan.rs | 29 +- .../coverage_fix_apply_silent_mute_exit.rs | 4 +- .../tests/covgap_commands_scan_hosted.rs | 42 +- .../socket-patch-cli/tests/e2e_bun_lockb.rs | 5 +- crates/socket-patch-cli/tests/e2e_cargo.rs | 6 +- crates/socket-patch-cli/tests/e2e_gem.rs | 6 +- crates/socket-patch-cli/tests/e2e_maven.rs | 3 +- crates/socket-patch-cli/tests/e2e_npm.rs | 6 +- crates/socket-patch-cli/tests/e2e_nuget.rs | 6 +- crates/socket-patch-cli/tests/e2e_pypi.rs | 6 +- .../tests/e2e_redirect_gem_build.rs | 5 +- .../tests/e2e_redirect_yarn_berry_build.rs | 6 +- .../tests/e2e_safety_cargo_build.rs | 6 +- .../socket-patch-cli/tests/e2e_safety_pnpm.rs | 18 +- .../tests/e2e_socket_yml_policy.rs | 471 ++++-------------- .../tests/e2e_vex_lockfile/common_selftest.rs | 6 +- .../tests/e2e_yarn4_pnpm_linker_build.rs | 16 +- .../tests/e2e_yarn4_workspaces_build.rs | 16 +- .../tests/get/get_edge_cases_e2e.rs | 12 +- .../tests/get/global_packages_e2e.rs | 5 +- .../tests/help_text_hygiene.rs | 31 +- .../tests/hosted_memory_engine.rs | 3 +- .../tests/hosted_memory_parity.rs | 183 ++----- .../tests/hosted_memory_rollout.rs | 42 +- .../tests/in_process_get_hosted_ecosystems.rs | 12 +- .../tests/in_process_redirect.rs | 7 +- .../tests/in_process_redirect/vlt.rs | 10 +- .../tests/in_process_redirect_pdm.rs | 30 +- .../tests/in_process_redirect_pipenv.rs | 73 +-- .../tests/in_process_redirect_pnpm.rs | 11 +- .../tests/in_process_rollback_hosted.rs | 5 +- .../tests/repair_vendor_flavors_e2e/vlt.rs | 5 +- .../rollback/rollback_duality_invariants.rs | 3 +- .../tests/scan/covgap_ecosystem_dispatch.rs | 8 +- .../tests/scan/scan_invariants.rs | 20 +- .../tests/scan/scan_paths_e2e.rs | 12 +- .../tests/update/covgap_commands_update.rs | 8 +- .../tests/yarn_berry_common/mod.rs | 4 +- crates/socket-patch-core/src/api/ranking.rs | 17 +- .../src/crawlers/python_crawler.rs | 10 +- .../src/formats/cargo/mod.rs | 12 +- .../src/formats/composer/mod.rs | 3 + .../src/formats/gem/gemfile.rs | 10 +- .../src/formats/gem/hosted.rs | 1 + .../socket-patch-core/src/formats/gem/mod.rs | 2 + crates/socket-patch-core/src/formats/mod.rs | 8 +- .../socket-patch-core/src/formats/pnpm/mod.rs | 76 +-- .../socket-patch-core/src/formats/registry.rs | 18 +- .../socket-patch-core/src/formats/yarn/mod.rs | 5 +- .../socket-patch-core/src/hosted/guidance.rs | 10 +- .../src/hosted/memory/discover.rs | 4 +- .../src/hosted/memory/limits.rs | 12 +- .../src/hosted/memory/mod.rs | 89 ++-- .../src/hosted/memory/roots.rs | 22 +- .../src/hosted/memory/select.rs | 14 +- .../src/hosted/memory/types.rs | 4 +- crates/socket-patch-core/src/ledgers.rs | 1 + crates/socket-patch-core/src/lib.rs | 1 + .../socket-patch-core/src/manifest/records.rs | 5 +- .../redirect/cargo_lock_equivalence_tests.rs | 4 +- .../redirect/golang_equivalence_tests.rs | 6 +- .../src/patch/redirect/mod.rs | 209 +++----- .../src/patch/redirect/npmrc.rs | 3 + .../src/patch/redirect/pdm.rs | 21 +- .../src/patch/redirect/pipenv.rs | 45 +- .../src/patch/redirect/poetry.rs | 18 +- .../src/patch/redirect/state.rs | 2 + .../src/patch/redirect/upstream/bun_lockb.rs | 5 +- .../src/patch/redirect/upstream/cargo.rs | 39 +- .../src/patch/redirect/upstream/gem.rs | 23 +- .../src/patch/redirect/upstream/golang.rs | 9 +- .../src/patch/redirect/upstream/mod.rs | 8 +- .../src/patch/redirect/upstream/pypi_locks.rs | 11 +- crates/socket-patch-core/src/policy/mod.rs | 7 +- crates/socket-patch-core/src/policy/report.rs | 4 +- .../src/policy/socket_yml.rs | 27 +- crates/socket-patch-core/src/policy/tests.rs | 29 +- crates/socket-patch-core/src/rollout/stage.rs | 11 +- crates/socket-patch-core/src/telemetry.rs | 4 +- .../socket-patch-core/src/update/download.rs | 13 +- .../socket-patch-core/src/update/release.rs | 39 +- .../src/utils/group_commit.rs | 21 +- .../src/utils/line_endings.rs | 1 + crates/socket-patch-core/src/utils/process.rs | 15 +- .../src/utils/python_script.rs | 7 +- .../socket-patch-core/src/vendor/bun_lockb.rs | 9 +- .../src/vendor/cargo_lock.rs | 4 +- crates/socket-patch-core/src/vendor/gem.rs | 14 +- .../src/vendor/lock_inventory/view.rs | 4 +- .../src/vendor/lock_inventory/wired.rs | 2 +- .../socket-patch-core/src/vendor/prestage.rs | 5 +- .../src/vendor/toml_surgery.rs | 3 +- .../src/vex/discover/cargo.rs | 29 +- .../src/vex/discover/maven.rs | 8 +- .../src/vex/discover/nuget.rs | 2 +- .../tests/covgap_api_blob_fetcher.rs | 5 +- .../tests/covgap_crawlers_composer_crawler.rs | 6 +- .../tests/hosted_inventory.rs | 10 +- .../socket-patch-core/tests/poetry_hosted.rs | 81 +-- .../tests/telemetry_helpers_e2e.rs | 6 +- .../tests/upstream_restore_golden.rs | 423 ++++------------ crates/socket-patch-core/tests/uv_hosted.rs | 4 +- crates/socket-patch-node/src/lib.rs | 6 +- 119 files changed, 793 insertions(+), 2180 deletions(-) diff --git a/crates/socket-patch-cli/src/commands/list.rs b/crates/socket-patch-cli/src/commands/list.rs index 44c719038..8fc77fab1 100644 --- a/crates/socket-patch-cli/src/commands/list.rs +++ b/crates/socket-patch-cli/src/commands/list.rs @@ -431,10 +431,7 @@ pub async fn run(args: ListArgs) -> i32 { detail: detail.clone(), }); } else if !args.common.silent { - eprintln!( - "Warning: {}", - crate::commands::rollback::capitalize_first(detail) - ); + eprintln!("Warning: {}", crate::commands::rollback::capitalize_first(detail)); } } let vendor_state = crate::commands::vendor_state_lenient(&loaded.vendor, args.common.silent); @@ -776,18 +773,12 @@ mod tests { let listings = HostedListing::from_pins( &[ pin("pkg:npm/minimist@1.2.2", &record.uuid), - pin( - "pkg:npm/other@1.0.0", - "33333333-3333-4333-8333-333333333333", - ), + pin("pkg:npm/other@1.0.0", "33333333-3333-4333-8333-333333333333"), ], Some(&legacy), ); assert_eq!(listings[0].record, record); - assert_eq!( - listings[1].record.uuid, - "33333333-3333-4333-8333-333333333333" - ); + assert_eq!(listings[1].record.uuid, "33333333-3333-4333-8333-333333333333"); assert!(listings[1].record.vulnerabilities.is_empty()); assert_eq!(listings[1].lockfiles, vec!["yarn.lock".to_string()]); } diff --git a/crates/socket-patch-cli/src/commands/mod.rs b/crates/socket-patch-cli/src/commands/mod.rs index 34ae4b1a3..ea45e6915 100644 --- a/crates/socket-patch-cli/src/commands/mod.rs +++ b/crates/socket-patch-cli/src/commands/mod.rs @@ -1,7 +1,7 @@ pub mod apply; pub(crate) mod bun_preflight; -pub(crate) mod composer_hints; pub(crate) mod context; +pub(crate) mod composer_hints; pub(crate) mod fetch_stage; pub mod get; pub mod hosted_bundle; @@ -9,11 +9,11 @@ pub mod list; pub(crate) mod lock_cli; pub mod remove; pub mod repair; +pub(crate) mod vendored_backend; pub mod rollback; pub mod scan; pub mod update; pub mod vendor; -pub(crate) mod vendored_backend; pub mod vex; pub(crate) mod vex_consumed; pub(crate) mod vex_sources; @@ -141,11 +141,9 @@ pub(crate) async fn hosted_state_from_lockfiles( common: &crate::args::GlobalArgs, root: &Path, ) -> socket_patch_core::patch::redirect::RedirectState { - hosted_state_from_pins( - &socket_patch_core::patch::redirect::upstream::HostedPin::all( - &discover_wiring(common, root).await, - ), - ) + hosted_state_from_pins(&socket_patch_core::patch::redirect::upstream::HostedPin::all( + &discover_wiring(common, root).await, + )) } /// [`hosted_state_from_lockfiles`] over already-discovered pins. A purl @@ -155,8 +153,10 @@ pub(crate) fn hosted_state_from_pins( ) -> socket_patch_core::patch::redirect::RedirectState { let mut state = socket_patch_core::patch::redirect::RedirectState::new(); for pin in pins { - state.records.entry(pin.purl.clone()).or_insert_with(|| { - socket_patch_core::manifest::schema::PatchRecord { + state + .records + .entry(pin.purl.clone()) + .or_insert_with(|| socket_patch_core::manifest::schema::PatchRecord { uuid: pin.uuid.clone(), exported_at: String::new(), files: Default::default(), @@ -164,8 +164,7 @@ pub(crate) fn hosted_state_from_pins( description: String::new(), license: String::new(), tier: String::new(), - } - }); + }); } state } @@ -192,3 +191,4 @@ pub(crate) fn vendor_state_lenient( } } } + diff --git a/crates/socket-patch-cli/src/commands/scan/discovery.rs b/crates/socket-patch-cli/src/commands/scan/discovery.rs index 79ca66737..f8e6b467c 100644 --- a/crates/socket-patch-cli/src/commands/scan/discovery.rs +++ b/crates/socket-patch-cli/src/commands/scan/discovery.rs @@ -168,32 +168,29 @@ pub(crate) async fn vendored_ledger_supplement( } // `(ledger key, base purl, entry)`; the artifact fallback has no // entries to probe, so it never reports unwired keys. - let candidates: Vec<( - String, - String, - Option<&socket_patch_core::vendor::VendorEntry>, - )> = match state { - Ok(state) => state - .entries - .iter() - .map(|(key, entry)| { - ( - key.clone(), - strip_purl_qualifiers(&entry.base_purl).to_string(), - Some(entry), - ) - }) - .collect(), - // Corrupt/unreadable ledger (a MISSING file is Ok(empty) above): - // recover the vendored set from the committed artifacts, or - // `scan --prune` (whose ledger exemption also degrades to empty) - // would delete still-vendored packages' manifest entries and blobs. - Err(_) => vendored_purls_from_artifacts(common) - .await - .into_iter() - .map(|base| (base.clone(), base, None)) - .collect(), - }; + let candidates: Vec<(String, String, Option<&socket_patch_core::vendor::VendorEntry>)> = + match state { + Ok(state) => state + .entries + .iter() + .map(|(key, entry)| { + ( + key.clone(), + strip_purl_qualifiers(&entry.base_purl).to_string(), + Some(entry), + ) + }) + .collect(), + // Corrupt/unreadable ledger (a MISSING file is Ok(empty) above): + // recover the vendored set from the committed artifacts, or + // `scan --prune` (whose ledger exemption also degrades to empty) + // would delete still-vendored packages' manifest entries and blobs. + Err(_) => vendored_purls_from_artifacts(common) + .await + .into_iter() + .map(|base| (base.clone(), base, None)) + .collect(), + }; // Composer by release identity: a ledger `@3.0.2.0` is the crawled // `@3.0.2`, not a second package to supplement. let key = |p: &str| composer_purl_identity(p).unwrap_or_else(|| normalize_purl(p).into_owned()); @@ -1048,9 +1045,7 @@ mod tests { ..GlobalArgs::default() }; let state = socket_patch_core::vendor::load_state(root).await; - vendored_ledger_supplement(&args, crawled, &state) - .await - .packages + vendored_ledger_supplement(&args, crawled, &state).await.packages } /// A ledger entry vendored as `@3.0.2.0` is the crawled composer @@ -1085,9 +1080,7 @@ mod tests { out.iter().map(|p| &p.purl).collect::>() ); - let out = vendored_ledger_supplement(&args, &[], &Ok(state)) - .await - .packages; + let out = vendored_ledger_supplement(&args, &[], &Ok(state)).await.packages; assert_eq!( out.iter().map(|p| p.purl.as_str()).collect::>(), vec!["pkg:composer/psr/log@3.0.2.0"] @@ -1190,10 +1183,7 @@ mod tests { let state = npm_ledger_with_lock(tmp.path(), lock.as_deref()).await; let out = vendored_ledger_supplement(&args, &[], &state).await; assert_eq!( - out.packages - .iter() - .map(|p| p.purl.as_str()) - .collect::>(), + out.packages.iter().map(|p| p.purl.as_str()).collect::>(), vec!["pkg:npm/left-pad@1.3.0"], "lock={lock:?}" ); diff --git a/crates/socket-patch-cli/src/commands/scan/hosted.rs b/crates/socket-patch-cli/src/commands/scan/hosted.rs index 6e2b0bea8..e6e48a140 100644 --- a/crates/socket-patch-cli/src/commands/scan/hosted.rs +++ b/crates/socket-patch-cli/src/commands/scan/hosted.rs @@ -982,8 +982,7 @@ pub(crate) async fn run_redirect_selected( socket_patch_core::utils::fs::read_regular_to_string_sync(path).ok() }) }; - let rewrite_options = || { - RewriteOptions { + let rewrite_options = || RewriteOptions { dry_run: common.dry_run, targets_pipenv_lock, pipenv_major, @@ -995,7 +994,6 @@ pub(crate) async fn run_redirect_selected( npm_allow_remote_config: !common.no_npm_allow_remote_config, npm_outer: &npm_outer, blocking: true, - } }; // The rollout gate plans again without its deferred rows: keep what // the second pass needs. @@ -2433,19 +2431,13 @@ fn join_names(names: &[String], max: usize) -> String { /// artifacts, then verify with `vex`. After a vendored→hosted takeover /// (`vendored_removed`) the commit also has to carry the deleted vendored /// ledger entries and artifacts. -fn format_next_steps( - files: &[String], - edits: &[socket_patch_core::patch::redirect::FileEdit], - vendored_removed: bool, -) -> Vec { +fn format_next_steps(files: &[String], edits: &[socket_patch_core::patch::redirect::FileEdit], vendored_removed: bool) -> Vec { if files.is_empty() && !vendored_removed { return Vec::new(); } let mut commit: Vec = Vec::new(); if vendored_removed { - commit.push( - ".socket/vendor/ (the removed vendored ledger entries and artifacts)".to_string(), - ); + commit.push(".socket/vendor/ (the removed vendored ledger entries and artifacts)".to_string()); } commit.extend(files.iter().cloned()); let npm = files @@ -4632,43 +4624,19 @@ mod tests { use super::npm_allow_remote_one_line; let hosts = ["patch.socket.dev"]; let cases = [ - ( - npm_allow_remote_configured_detail(&hosts, true, false), - "Note: set", - ), - ( - npm_allow_remote_configured_detail(&hosts, false, false), - "Note: set", - ), - ( - npm_allow_remote_configured_detail(&hosts, true, true), - "Note: would set", - ), - ( - npm_allow_remote_already_detail(&hosts), - "Note: .npmrc already", - ), - ( - npm_allow_remote_user_set_detail(&hosts, "none"), - "Warning: npm >=12", - ), - ( - npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), - "Warning: npm >=12", - ), + (npm_allow_remote_configured_detail(&hosts, true, false), "Note: set"), + (npm_allow_remote_configured_detail(&hosts, false, false), "Note: set"), + (npm_allow_remote_configured_detail(&hosts, true, true), "Note: would set"), + (npm_allow_remote_already_detail(&hosts), "Note: .npmrc already"), + (npm_allow_remote_user_set_detail(&hosts, "none"), "Warning: npm >=12"), + (npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), "Warning: npm >=12"), (npm_allow_remote_manual_detail(&hosts), "Warning: npm >=12"), - ( - npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), - "Warning: npm >=12", - ), + (npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), "Warning: npm >=12"), ]; for (detail, start) in cases { let line = npm_allow_remote_one_line(&detail); assert!(line.starts_with(start), "{line}"); - assert!( - !line.contains('\n') && line.ends_with("(details: --verbose)."), - "{line}" - ); + assert!(!line.contains('\n') && line.ends_with("(details: --verbose)."), "{line}"); } } } diff --git a/crates/socket-patch-cli/src/commands/scan/policy.rs b/crates/socket-patch-cli/src/commands/scan/policy.rs index 9e53b7a55..98b1ecc45 100644 --- a/crates/socket-patch-cli/src/commands/scan/policy.rs +++ b/crates/socket-patch-cli/src/commands/scan/policy.rs @@ -11,9 +11,9 @@ use socket_patch_core::api::ranking::cmp_search_results; use socket_patch_core::api::types::PatchSearchResult; use socket_patch_core::manifest::schema::PatchManifest; use socket_patch_core::policy::{ - canon, find_repo_root_with_warnings, patch_severity_order, policy_block, repo_relative_checked, - sanitize, severity_name, DiskPolicyFs, FilterReason, FilteredEntry, Offers, PolicyError, - PolicySource, PolicyWarning, RetainedEntry, Root, SelectionPolicy, PATCHES_DISABLED, + canon, find_repo_root_with_warnings, policy_block, FilteredEntry, RetainedEntry, patch_severity_order, repo_relative_checked, sanitize, severity_name, + DiskPolicyFs, FilterReason, Offers, PolicyError, PolicySource, PolicyWarning, Root, SelectionPolicy, + PATCHES_DISABLED, }; use socket_patch_core::utils::purl::normalize_purl; @@ -42,18 +42,12 @@ pub(crate) struct InvocationPolicy { /// Load the policy for `args` (4.5): `--global` scans have no repo and read /// no file; everything else reads the repo root's socket.yml. pub(crate) fn load_invocation_policy(args: &ScanArgs) -> Result { - let overrides = args - .socket_yml - .overrides() - .map_err(PolicyLoadError::Usage)?; + let overrides = args.socket_yml.overrides().map_err(PolicyLoadError::Usage)?; let cwd = std::fs::canonicalize(&args.common.cwd).unwrap_or_else(|_| args.common.cwd.clone()); if args.common.is_global() { - let policy = SelectionPolicy::load( - &socket_patch_core::policy::MemoryPolicyFs::default(), - &overrides, - ) - .map_err(PolicyLoadError::Policy)? - .0; + let policy = SelectionPolicy::load(&socket_patch_core::policy::MemoryPolicyFs::default(), &overrides) + .map_err(PolicyLoadError::Policy)? + .0; return Ok(InvocationPolicy { policy, repo_root: cwd, @@ -62,8 +56,8 @@ pub(crate) fn load_invocation_policy(args: &ScanArgs) -> Result Self { + pub(crate) fn for_root(invocation: &InvocationPolicy, root_dir: &Path, explicit: bool, global: bool) -> Self { let root_dir = std::fs::canonicalize(root_dir).unwrap_or_else(|_| root_dir.to_path_buf()); let project = repo_relative_checked(&invocation.repo_root, &root_dir).unwrap_or_default(); let root_verdict = if global { @@ -182,9 +171,7 @@ impl ScanPolicy { severity: None, }); } - let announce_warnings = !invocation - .warned - .swap(true, std::sync::atomic::Ordering::Relaxed); + let announce_warnings = !invocation.warned.swap(true, std::sync::atomic::Ordering::Relaxed); Self { policy: invocation.policy.clone(), warnings, @@ -237,10 +224,7 @@ impl ScanPolicy { /// exclude stays in the query (so `upgradeAvailable` can be reported) /// but joins the retained set, which never reaches a writer. pub(crate) fn admit_crawled(&self, purl: &str) -> bool { - let verdict = self - .root_verdict - .clone() - .and_then(|()| self.policy.admits_purl(purl)); + let verdict = self.root_verdict.clone().and_then(|()| self.policy.admits_purl(purl)); let reason = match verdict { Ok(()) => return true, Err(reason) => reason, @@ -350,8 +334,7 @@ impl ScanPolicy { // (not when a lower-ranked admitted patch simply wins). let top_withheld = self.policy.admits_severity(patch_severity_order(&group[0])); if let Err(reason) = top_withheld { - let upgrade_withheld = - chosen.is_some() && chosen == recorded_at && recorded_at != Some(0); + let upgrade_withheld = chosen.is_some() && chosen == recorded_at && recorded_at != Some(0); if chosen.is_none() || upgrade_withheld { report.filtered.push(FilteredEntry { purl: Some(canon(&purl)), @@ -539,20 +522,17 @@ pub(crate) fn policy_bypass_warnings( let verdict = if !policy.enabled() { Err(FilterReason::Disabled) } else { - root_verdict - .clone() - .and_then(|()| policy.admits_purl(purl)) - .and_then(|()| { - // The floor only hides a package when none of its patches pass. - match group - .iter() - .map(|p| policy.admits_severity(patch_severity_order(p))) - .find(Result::is_ok) - { - Some(ok) => ok, - None => policy.admits_severity(patch_severity_order(group[0])), - } - }) + root_verdict.clone().and_then(|()| policy.admits_purl(purl)).and_then(|()| { + // The floor only hides a package when none of its patches pass. + match group + .iter() + .map(|p| policy.admits_severity(patch_severity_order(p))) + .find(Result::is_ok) + { + Some(ok) => ok, + None => policy.admits_severity(patch_severity_order(group[0])), + } + }) }; if let Err(reason) = verdict { out.push(( diff --git a/crates/socket-patch-cli/src/commands/scan/rollout.rs b/crates/socket-patch-cli/src/commands/scan/rollout.rs index fe7470a83..82ef99e17 100644 --- a/crates/socket-patch-cli/src/commands/scan/rollout.rs +++ b/crates/socket-patch-cli/src/commands/scan/rollout.rs @@ -4,10 +4,8 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; +use socket_patch_core::rollout::{canonical_base_purl, severity_label, MaxNew, MaxNewSource, Recorded, RolloutPlan}; pub(crate) use socket_patch_core::rollout::stage::*; -use socket_patch_core::rollout::{ - canonical_base_purl, severity_label, MaxNew, MaxNewSource, Recorded, RolloutPlan, -}; use super::discovery::UpdateInfo; @@ -210,11 +208,11 @@ pub(crate) fn human_lines( mod tests { use super::*; use socket_patch_core::api::types::PatchSearchResult; - use socket_patch_core::api::types::VulnerabilityResponse; use socket_patch_core::manifest::schema::PatchManifest; + use std::path::Path; + use socket_patch_core::api::types::VulnerabilityResponse; use socket_patch_core::manifest::schema::PatchRecord; use std::collections::HashMap; - use std::path::Path; fn offer(purl: &str, uuid: &str, published: &str, severities: &[&str]) -> PatchSearchResult { PatchSearchResult { @@ -359,21 +357,13 @@ mod tests { let stored = manifest(&[("pkg:composer/psr/log@3.0.2.0", "old")]); let recorded = RecordedIndex::new(Some(&stored), &[]); let offers = offers_from_results( - &[offer( - "pkg:composer/psr/log@v3.0.2", - "new", - "2026-02-01T00:00:00Z", - &["high"], - )], + &[offer("pkg:composer/psr/log@v3.0.2", "new", "2026-02-01T00:00:00Z", &["high"])], false, ); let rows = classify(&offers, &recorded, ""); let plan = socket_patch_core::rollout::plan_rollout( rows.into_iter().map(|row| row.candidate).collect(), - &MaxNew { - value: Some(0), - source: MaxNewSource::Flag, - }, + &MaxNew { value: Some(0), source: MaxNewSource::Flag }, false, &BTreeSet::new(), ); diff --git a/crates/socket-patch-cli/src/commands/scan/rollout_args.rs b/crates/socket-patch-cli/src/commands/scan/rollout_args.rs index f83636045..e4d251e98 100644 --- a/crates/socket-patch-cli/src/commands/scan/rollout_args.rs +++ b/crates/socket-patch-cli/src/commands/scan/rollout_args.rs @@ -1,6 +1,7 @@ //! `scan --max-new-patches` (see the rollout guide, //! `docs/configuration.md#gradual-rollout`). + use clap::Args; pub(crate) use socket_patch_core::rollout::stage::RolloutCarry; use socket_patch_core::rollout::{resolve_max_new, MaxNew}; @@ -76,6 +77,7 @@ impl RolloutArgs { } } + #[cfg(test)] mod tests { use super::*; diff --git a/crates/socket-patch-cli/src/commands/vendor.rs b/crates/socket-patch-cli/src/commands/vendor.rs index e9eedafe3..0289bf946 100644 --- a/crates/socket-patch-cli/src/commands/vendor.rs +++ b/crates/socket-patch-cli/src/commands/vendor.rs @@ -283,7 +283,10 @@ pub(crate) async fn dispatch_revert_one_opts( /// entry (fail-safe): ecosystems other than npm, cargo and pypi (whose /// probe covers the requirements flavor only) have no in-use probe yet, /// and a missing/unreadable lockfile proves nothing. -pub(crate) async fn dispatch_in_use_one(entry: &VendorEntry, project_root: &Path) -> Option { +pub(crate) async fn dispatch_in_use_one( + entry: &VendorEntry, + project_root: &Path, +) -> Option { match entry.ecosystem.as_str() { "npm" => vendor::npm_flavor::vendored_entry_in_use(entry, project_root).await, // Cargo probes the lock entry's shape: detached + `[patch]` pointing diff --git a/crates/socket-patch-cli/tests/apply/apply_network.rs b/crates/socket-patch-cli/tests/apply/apply_network.rs index 7284a5e2f..837057e18 100644 --- a/crates/socket-patch-cli/tests/apply/apply_network.rs +++ b/crates/socket-patch-cli/tests/apply/apply_network.rs @@ -940,10 +940,7 @@ async fn apply_online_ignores_legacy_package_archive_when_downloads_fail() { "a legacy package archive must not cover the patch; stdout={stdout}\nstderr={stderr}" ); let content = std::fs::read(tmp.path().join("node_modules/pkgcache/index.js")).unwrap(); - assert_eq!( - content, before, - "the file must not be patched from the legacy archive" - ); + assert_eq!(content, before, "the file must not be patched from the legacy archive"); let requests = mock.received_requests().await.unwrap_or_default(); let blob_path = format!("/v0/orgs/{ORG_SLUG}/patches/blob/{after_hash}"); @@ -1046,7 +1043,10 @@ async fn mismatch_blob_topup_probes_every_copy_of_a_duplicated_package() { v["summary"]["applied"], 1, "the drifted nested copy must be warn-overwritten.\nstdout={v:#}" ); - assert_eq!(v["summary"]["failed"], 0, "no copy may fail.\nstdout={v:#}"); + assert_eq!( + v["summary"]["failed"], 0, + "no copy may fail.\nstdout={v:#}" + ); // The nested copy's blob was fetched on demand… let requests = mock.received_requests().await.unwrap(); diff --git a/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs b/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs index 5bc4eacd7..6e849f90c 100644 --- a/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs +++ b/crates/socket-patch-cli/tests/apply/in_process_gem_config_warning.rs @@ -201,9 +201,7 @@ fn apply_stderr_warning_gates_on_silent() { "non-silent stderr must carry the {CODE} warning; got:\n{stderr}" ); assert_eq!( - stderr - .matches("Warning: bundler app config BUNDLE_PATH") - .count(), + stderr.matches("Warning: bundler app config BUNDLE_PATH").count(), 1, "exactly ONE warning line (not one per discovery call); got:\n{stderr}" ); diff --git a/crates/socket-patch-cli/tests/cli/covgap_output.rs b/crates/socket-patch-cli/tests/cli/covgap_output.rs index 1f5e1c860..65cf0b67f 100644 --- a/crates/socket-patch-cli/tests/cli/covgap_output.rs +++ b/crates/socket-patch-cli/tests/cli/covgap_output.rs @@ -168,8 +168,9 @@ fn run_in_pty_inner( .expect("spawn socket-patch in PTY"); drop(pair.slave); - let reader_handle = - crate::pty_io::PtyOutput::spawn(pair.master.try_clone_reader().expect("clone reader")); + let reader_handle = crate::pty_io::PtyOutput::spawn( + pair.master.try_clone_reader().expect("clone reader"), + ); // Watchdog: detached kill after `timeout`; a no-op if the child exits // naturally first. @@ -260,10 +261,7 @@ fn remove_interactive_bare_enter_proceeds_with_default_yes() { "\n", Duration::from_secs(15), ); - assert_eq!( - code, 0, - "remove with bare Enter must succeed; got: {output}" - ); + assert_eq!(code, 0, "remove with bare Enter must succeed; got: {output}"); // The interactive confirm MUST have run — otherwise this test passes // vacuously against a regression that drops the TTY gate and // auto-proceeds. Match the distinctive prompt verbatim (the loose diff --git a/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs b/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs index a7387e225..6f744bfe4 100644 --- a/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs +++ b/crates/socket-patch-cli/tests/cli/interactive_prompts_e2e.rs @@ -112,8 +112,9 @@ fn run_in_pty_bytes(args: &[&str], cwd: &Path, input: &[u8], timeout: Duration) // closed. The previous design used a chunked read+mpsc loop // because it interleaved with a try_wait poll; the simplified // design serializes wait → drop master → read_to_end joins. - let reader_handle = - crate::pty_io::PtyOutput::spawn(pair.master.try_clone_reader().expect("clone reader")); + let reader_handle = crate::pty_io::PtyOutput::spawn( + pair.master.try_clone_reader().expect("clone reader"), + ); // Watchdog: detach a thread that kills the child after `timeout`. // The cloned ChildKiller is independent of the main `child` diff --git a/crates/socket-patch-cli/tests/cli_config_fallback.rs b/crates/socket-patch-cli/tests/cli_config_fallback.rs index 530a53c5f..df19585db 100644 --- a/crates/socket-patch-cli/tests/cli_config_fallback.rs +++ b/crates/socket-patch-cli/tests/cli_config_fallback.rs @@ -59,7 +59,8 @@ fn scan_cmd(project: &Path, data_dir: &Path) -> Command { let mut cmd = Command::new(BINARY); // Human mode: core's proxy advisory (the oracle below) is muted under // `--json`/`--silent`. - cmd.args(["scan", "-e", "npm", "--cwd"]).arg(project); + cmd.args(["scan", "-e", "npm", "--cwd"]) + .arg(project); for (key, _) in std::env::vars_os() { let name = key.to_string_lossy(); if name.starts_with("SOCKET_") { @@ -297,9 +298,7 @@ async fn corrupt_config_warns_and_keeps_json_stdout_clean() { json_cmd.arg("--json"); let json_out = run(json_cmd); assert!( - json_out - .stderr - .contains("could not parse socket-cli config"), + json_out.stderr.contains("could not parse socket-cli config"), "the parse warning must reach stderr under --json too; got:\n{}", json_out.stderr ); diff --git a/crates/socket-patch-cli/tests/cli_get_silent.rs b/crates/socket-patch-cli/tests/cli_get_silent.rs index 72f454a6a..4e43c353d 100644 --- a/crates/socket-patch-cli/tests/cli_get_silent.rs +++ b/crates/socket-patch-cli/tests/cli_get_silent.rs @@ -25,7 +25,10 @@ fn run_get(cwd: &Path, args: &[&str]) -> (i32, String) { for var in GLOBAL_ARG_ENV_VARS { cmd.env_remove(var); } - for var in ["SOCKET_SAVE_ONLY", "SOCKET_ALL_RELEASES"] { + for var in [ + "SOCKET_SAVE_ONLY", + "SOCKET_ALL_RELEASES", + ] { cmd.env_remove(var); } cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); diff --git a/crates/socket-patch-cli/tests/cli_parse_list.rs b/crates/socket-patch-cli/tests/cli_parse_list.rs index 9a850490d..8c997686f 100644 --- a/crates/socket-patch-cli/tests/cli_parse_list.rs +++ b/crates/socket-patch-cli/tests/cli_parse_list.rs @@ -370,11 +370,7 @@ fn missing_manifest_under_valid_cwd_is_not_an_error_via_binary() { let out = run_list_binary(tmp.path(), &["--json"]); let v: serde_json::Value = serde_json::from_str(String::from_utf8_lossy(&out.stdout).trim()) .expect("stdout must be valid JSON envelope"); - assert_eq!( - out.status.code(), - Some(0), - "missing manifest is an empty list" - ); + assert_eq!(out.status.code(), Some(0), "missing manifest is an empty list"); assert_eq!(v["status"], "success", "envelope: {v}"); assert_eq!(v["summary"]["discovered"], 0, "envelope: {v}"); } @@ -1317,10 +1313,7 @@ fn missing_manifest_with_corrupt_ledger_keeps_warning_in_the_envelope_via_binary assert_eq!(v["status"], "success", "envelope={v}"); let warnings = v["warnings"].as_array().expect("warnings[] present"); assert_eq!(warnings.len(), 1, "envelope={v}"); - assert_eq!( - warnings[0]["code"], "redirect_ledger_corrupt", - "envelope={v}" - ); + assert_eq!(warnings[0]["code"], "redirect_ledger_corrupt", "envelope={v}"); assert!( out.stderr.is_empty(), "--json must keep stderr clean: {}", diff --git a/crates/socket-patch-cli/tests/cli_parse_rollback.rs b/crates/socket-patch-cli/tests/cli_parse_rollback.rs index f1590292e..c8b77af5e 100644 --- a/crates/socket-patch-cli/tests/cli_parse_rollback.rs +++ b/crates/socket-patch-cli/tests/cli_parse_rollback.rs @@ -366,11 +366,7 @@ fn bare_bool_does_not_consume_next_token() { /// relied on the rejection get a test-visible flip instead of a silent one. #[test] fn multiple_targets_parse_in_order() { - let args = parse_rollback(&[ - "pkg:npm/foo@1", - "packages/api/**", - "b0630680-4da6-45f9-bba8-b888e0ffd58c", - ]); + let args = parse_rollback(&["pkg:npm/foo@1", "packages/api/**", "b0630680-4da6-45f9-bba8-b888e0ffd58c"]); assert_eq!( args.targets, vec![ diff --git a/crates/socket-patch-cli/tests/cli_parse_scan.rs b/crates/socket-patch-cli/tests/cli_parse_scan.rs index eff55ee79..ab81fa6eb 100644 --- a/crates/socket-patch-cli/tests/cli_parse_scan.rs +++ b/crates/socket-patch-cli/tests/cli_parse_scan.rs @@ -898,11 +898,7 @@ fn max_new_patches_takes_a_count_or_none() { ("NONE", None), ] { let args = parse_scan(&["--max-new-patches", raw]); - assert_eq!( - args.rollout.max_new_patches, - Some(MaxNewPatches(want)), - "{raw}" - ); + assert_eq!(args.rollout.max_new_patches, Some(MaxNewPatches(want)), "{raw}"); } } @@ -993,33 +989,20 @@ fn min_severity_flag_and_env() { assert_eq!(parse_scan(&[]).socket_yml.min_severity, None); assert_eq!(overrides(&[], &[]).unwrap().min_severity, None); assert_eq!( - overrides(&["--min-severity", "High"], &[]) - .unwrap() - .min_severity, + overrides(&["--min-severity", "High"], &[]).unwrap().min_severity, Some((Some(1), OverrideSource::Flag)) ); assert_eq!( - overrides( - &["--min-severity", "none"], - &[("SOCKET_MIN_SEVERITY", "critical")] - ) - .unwrap() - .min_severity, + overrides(&["--min-severity", "none"], &[("SOCKET_MIN_SEVERITY", "critical")]).unwrap().min_severity, Some((None, OverrideSource::Flag)) ); assert_eq!( - overrides(&[], &[("SOCKET_MIN_SEVERITY", "moderate")]) - .unwrap() - .min_severity, + overrides(&[], &[("SOCKET_MIN_SEVERITY", "moderate")]).unwrap().min_severity, Some((Some(2), OverrideSource::Env)) ); - assert_eq!( - overrides(&[], &[("SOCKET_MIN_SEVERITY", "")]) - .unwrap() - .min_severity, - None - ); + assert_eq!(overrides(&[], &[("SOCKET_MIN_SEVERITY", "")]).unwrap().min_severity, None); assert!(overrides(&[], &[("SOCKET_MIN_SEVERITY", "severe")]).is_err()); assert!(try_parse_scan(&["--min-severity", "severe"]).is_err()); assert!(overrides(&["--no-socket-yml"], &[]).unwrap().bypass); } + diff --git a/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs b/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs index 049d8356b..444dd2a3b 100644 --- a/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs +++ b/crates/socket-patch-cli/tests/coverage_fix_apply_silent_mute_exit.rs @@ -149,9 +149,7 @@ fn apply_silent_online_download_failure_keeps_error_output() { ); let chatter = stderr_chatter(&stderr); assert!( - chatter - .iter() - .any(|l| l.contains("could not be downloaded")), + chatter.iter().any(|l| l.contains("could not be downloaded")), "--silent must keep the download-failure error (errors only, \ never nothing); stderr was: {stderr:?}" ); diff --git a/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs b/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs index 235030104..54ddf7441 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs +++ b/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs @@ -566,7 +566,8 @@ async fn wet_takeover_refuses_unrevertable_vendored_flavor_fail_closed() { "the human skipped line must name purl + reason; stderr=\n{stderr}" ); assert!( - stderr.contains("Warning: ") && stderr.contains("could not be reverted"), + stderr.contains("Warning: ") + && stderr.contains("could not be reverted"), "the takeover pre-warning must reach human stderr; stderr=\n{stderr}" ); } @@ -813,10 +814,7 @@ async fn zero_grant_wet_run_ignores_a_malformed_pre_v5_ledger() { let lock_before = std::fs::read(root.join("package-lock.json")).unwrap(); let assert_ignored = |code: i32, doc: &Value, label: &str| { - assert_eq!( - code, 0, - "{label}: a pre-v5 ledger is never an error: {doc:#}" - ); + assert_eq!(code, 0, "{label}: a pre-v5 ledger is never an error: {doc:#}"); assert_eq!(doc["status"], "success", "{label}: {doc:#}"); assert!( !doc.to_string().contains("redirect-state.json"), @@ -1019,10 +1017,7 @@ async fn hosted_human_empty_discovery_ignores_a_malformed_pre_v5_ledger() { for extra in [&[][..], &["--silent"][..]] { let (code, stdout, stderr) = scan_hosted(root, &server.uri(), extra, &[]); - assert_eq!( - code, 0, - "{extra:?}: an empty discovery exits 0; stderr=\n{stderr}" - ); + assert_eq!(code, 0, "{extra:?}: an empty discovery exits 0; stderr=\n{stderr}"); if extra.is_empty() { assert!( stdout.contains("No patches available for installed packages."), @@ -1409,22 +1404,16 @@ async fn native_bun_lockb_hosting_dry_run_rerun_and_rollback_without_bun() { ], &env, ); - assert_eq!( - code, 1, - "a binary bun.lockb pin is refused: {stdout}\n{stderr}" - ); + assert_eq!(code, 1, "a binary bun.lockb pin is refused: {stdout}\n{stderr}"); let doc: Value = serde_json::from_str(&stdout).unwrap_or_else(|e| panic!("{e}: {stdout}")); assert_eq!(doc["status"], "partial_failure", "{doc:#}"); - let failed = doc["hosted"]["failed"] - .as_array() - .unwrap_or_else(|| panic!("{doc:#}")); + let failed = doc["hosted"]["failed"].as_array().unwrap_or_else(|| panic!("{doc:#}")); assert_eq!(failed.len(), 1, "{doc:#}"); assert_eq!(failed[0]["purl"], purl, "{doc:#}"); let error = failed[0]["error"].as_str().unwrap_or_default(); assert!( - error.starts_with(&format!( - "cannot restore {purl} to its upstream registry entry: " - )) && error.contains("bun.lockb") + error.starts_with(&format!("cannot restore {purl} to its upstream registry entry: ")) + && error.contains("bun.lockb") && error.contains("git checkout"), "{error}" ); @@ -1830,9 +1819,7 @@ async fn unreadable_pnpm_workspace_gets_warning_only_guidance_in_a_live_run() { "the unreadable workspace file must be left byte-identical" ); assert!( - !tmp.path() - .join(".socket/vendor/redirect-state.json") - .exists(), + !tmp.path().join(".socket/vendor/redirect-state.json").exists(), "v5 hosted mode writes no redirect ledger" ); } @@ -1944,8 +1931,9 @@ async fn live_hosted_overlap_fires_redirect_supersedes_vendored() { let (code, _stdout, stderr) = scan_hosted(root, &server.uri(), &psu, &[]); assert_eq!(code, 0, "human overlap run exits 0; stderr=\n{stderr}"); assert!( - stderr.contains("Warning: Hosted wiring superseded the vendored ledger for:") - && stderr.contains(XPURL), + stderr.contains( + "Warning: Hosted wiring superseded the vendored ledger for:" + ) && stderr.contains(XPURL), "the supersedes warning must reach human stderr; stderr=\n{stderr}" ); } @@ -1993,7 +1981,8 @@ async fn human_dry_run_prints_would_rewrite_pnpm_guidance_and_vex_skip() { "the requested-but-skipped VEX must be announced; stderr=\n{stderr}" ); assert!( - stderr.contains("Warning: ") && stderr.contains("trustLockfile"), + stderr.contains("Warning: ") + && stderr.contains("trustLockfile"), "the pnpm trust guidance must reach human stderr; stderr=\n{stderr}" ); assert!( @@ -2440,8 +2429,7 @@ async fn human_pnpm_rerun_prints_only_the_reminder_and_heal_restores_guidance() let (code, stdout, stderr) = scan_hosted(root, &server.uri(), &[], &[]); assert_eq!(code, 0, "stdout=\n{stdout}\nstderr=\n{stderr}"); assert!( - engine_stdout(&stdout) - .starts_with("Switched 1 package to hosted patches; rewrote 2 files.\n"), + engine_stdout(&stdout).starts_with("Switched 1 package to hosted patches; rewrote 2 files.\n"), "{stdout}" ); // Everything from the pnpm warning on (the lines above it are the diff --git a/crates/socket-patch-cli/tests/e2e_bun_lockb.rs b/crates/socket-patch-cli/tests/e2e_bun_lockb.rs index 60f369c1e..413bb91a6 100644 --- a/crates/socket-patch-cli/tests/e2e_bun_lockb.rs +++ b/crates/socket-patch-cli/tests/e2e_bun_lockb.rs @@ -1156,10 +1156,7 @@ async fn workspace_text_migration_heals_on_rerun() { } let output = command(&fixture.reader, &checkout) .args(["install", "--frozen-lockfile", "--ignore-scripts"]) - .env( - "BUN_INSTALL_CACHE_DIR", - fixture.temp.path().join("text-cache"), - ) + .env("BUN_INSTALL_CACHE_DIR", fixture.temp.path().join("text-cache")) .env("BUN_INSTALL", fixture.temp.path().join("text-home")) .output() .unwrap(); diff --git a/crates/socket-patch-cli/tests/e2e_cargo.rs b/crates/socket-patch-cli/tests/e2e_cargo.rs index 73c6acaef..3978aff96 100644 --- a/crates/socket-patch-cli/tests/e2e_cargo.rs +++ b/crates/socket-patch-cli/tests/e2e_cargo.rs @@ -204,7 +204,8 @@ async fn scan_discovers_fake_registry_crates() { "Expected human scan to report exactly 'Found 2 packages (2 cargo)', got:\n{combined}" ); assert!( - !combined.contains("No packages found") && !combined.contains("No packages found"), + !combined.contains("No packages found") + && !combined.contains("No packages found"), "scan reported no packages despite a populated registry:\n{combined}" ); @@ -261,7 +262,8 @@ async fn scan_discovers_vendor_crates() { "Expected human scan to report exactly 'Found 1 package (1 cargo)', got:\n{combined}" ); assert!( - !combined.contains("No packages found") && !combined.contains("No packages found"), + !combined.contains("No packages found") + && !combined.contains("No packages found"), "scan reported no packages despite a populated vendor dir:\n{combined}" ); diff --git a/crates/socket-patch-cli/tests/e2e_gem.rs b/crates/socket-patch-cli/tests/e2e_gem.rs index f6f189113..db8af0af8 100644 --- a/crates/socket-patch-cli/tests/e2e_gem.rs +++ b/crates/socket-patch-cli/tests/e2e_gem.rs @@ -583,11 +583,7 @@ fn test_gem_dry_run() { let gem_dir = find_gem_dir(cwd); // Download without applying. - assert_run_ok( - cwd, - &["get", GEM_UUID, "--mode", "agent", "--no-apply"], - "get --no-apply", - ); + assert_run_ok(cwd, &["get", GEM_UUID, "--mode", "agent", "--no-apply"], "get --no-apply"); // Read manifest to get file list and expected hashes. let manifest_path = cwd.join(".socket/manifest.json"); diff --git a/crates/socket-patch-cli/tests/e2e_maven.rs b/crates/socket-patch-cli/tests/e2e_maven.rs index 00937ae6c..22d7e940d 100644 --- a/crates/socket-patch-cli/tests/e2e_maven.rs +++ b/crates/socket-patch-cli/tests/e2e_maven.rs @@ -177,7 +177,8 @@ async fn scan_discovers_maven_artifacts() { // Must NOT have hit the empty-crawl path — that line *also* contains // the word "packages". assert!( - !combined.contains("No packages found") && !combined.contains("No packages found"), + !combined.contains("No packages found") + && !combined.contains("No packages found"), "scan reported zero packages — Maven discovery did not run:\n{combined}" ); assert!( diff --git a/crates/socket-patch-cli/tests/e2e_npm.rs b/crates/socket-patch-cli/tests/e2e_npm.rs index 7486a85c9..89f40f9a5 100644 --- a/crates/socket-patch-cli/tests/e2e_npm.rs +++ b/crates/socket-patch-cli/tests/e2e_npm.rs @@ -286,11 +286,7 @@ fn test_npm_dry_run() { assert_eq!(git_sha256_file(&index_js), BEFORE_HASH); // Download the patch *without* applying. - assert_run_ok( - cwd, - &["get", NPM_UUID, "--mode", "agent", "--no-apply"], - "get --no-apply", - ); + assert_run_ok(cwd, &["get", NPM_UUID, "--mode", "agent", "--no-apply"], "get --no-apply"); // File should still be original. assert_eq!( diff --git a/crates/socket-patch-cli/tests/e2e_nuget.rs b/crates/socket-patch-cli/tests/e2e_nuget.rs index f8ec8eb1e..ce4cc4998 100644 --- a/crates/socket-patch-cli/tests/e2e_nuget.rs +++ b/crates/socket-patch-cli/tests/e2e_nuget.rs @@ -227,8 +227,7 @@ async fn scan_discovers_global_cache_packages() { // "packages" substring check would also match). assert!( !combined.contains("No packages found") - && !combined.contains("No packages found") - && !combined.contains("No global packages found"), + && !combined.contains("No packages found") && !combined.contains("No global packages found"), "scan failed to discover the fake global cache:\n{combined}" ); // Exactly the two packages we planted (Newtonsoft.Json, System.Text.Json), @@ -286,8 +285,7 @@ async fn scan_discovers_legacy_packages() { ); assert!( !combined.contains("No packages found") - && !combined.contains("No packages found") - && !combined.contains("No global packages found"), + && !combined.contains("No packages found") && !combined.contains("No global packages found"), "scan failed to discover the legacy packages/ layout:\n{combined}" ); // Exactly the single legacy package we planted (Newtonsoft.Json.13.0.3), diff --git a/crates/socket-patch-cli/tests/e2e_pypi.rs b/crates/socket-patch-cli/tests/e2e_pypi.rs index d84c6db20..4531d1173 100644 --- a/crates/socket-patch-cli/tests/e2e_pypi.rs +++ b/crates/socket-patch-cli/tests/e2e_pypi.rs @@ -426,11 +426,7 @@ fn test_pypi_dry_run() { let original_hash = git_sha256_file(&messages_py); // Download without applying. - assert_run_ok( - cwd, - &["get", PYPI_UUID, "--mode", "agent", "--no-apply"], - "get --no-apply", - ); + assert_run_ok(cwd, &["get", PYPI_UUID, "--mode", "agent", "--no-apply"], "get --no-apply"); // File should be unchanged. assert_eq!( diff --git a/crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs b/crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs index 9da70ac65..bd737a2ca 100644 --- a/crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs +++ b/crates/socket-patch-cli/tests/e2e_redirect_gem_build.rs @@ -1856,10 +1856,7 @@ async fn gem_hosted_custom_git_source_is_refused_and_still_installs() { Driver::ScanVexCustomGitSource, ) .await; - assert!( - fx.is_none(), - "the custom git_source driver asserts in place" - ); + assert!(fx.is_none(), "the custom git_source driver asserts in place"); } /// #340: a `gem` declaration that continues on the next line must not be diff --git a/crates/socket-patch-cli/tests/e2e_redirect_yarn_berry_build.rs b/crates/socket-patch-cli/tests/e2e_redirect_yarn_berry_build.rs index 4c96ef1b3..c1ae3226c 100644 --- a/crates/socket-patch-cli/tests/e2e_redirect_yarn_berry_build.rs +++ b/crates/socket-patch-cli/tests/e2e_redirect_yarn_berry_build.rs @@ -592,9 +592,9 @@ async fn berry_hosted_project_with( let root_pkg = std::fs::read_to_string(proj.join("package.json")).unwrap(); let root_pkg: serde_json::Value = serde_json::from_str(&root_pkg).unwrap(); assert!( - root_pkg["resolutions"].as_object().is_some_and(|r| r - .iter() - .any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) + root_pkg["resolutions"] + .as_object() + .is_some_and(|r| r.iter().any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) && v.as_str() == Some(hosted_url.as_str()))), "package.json must route {DEP} to the hosted tarball: {root_pkg}" ); diff --git a/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs b/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs index 29e90c39d..62e9ef05d 100644 --- a/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs +++ b/crates/socket-patch-cli/tests/e2e_safety_cargo_build.rs @@ -419,11 +419,7 @@ fn manifestless_agent_patch_is_not_attested(consumer: &Path, cargo_home: &Path) "description": "d" } }); - std::fs::write( - &manifest_path, - serde_json::to_vec_pretty(&manifest).unwrap(), - ) - .unwrap(); + std::fs::write(&manifest_path, serde_json::to_vec_pretty(&manifest).unwrap()).unwrap(); let out = run_vex(&bin, consumer, &run); assert_eq!(out.code, Some(0), "manifest-backed vex:\n{out}"); assert!( diff --git a/crates/socket-patch-cli/tests/e2e_safety_pnpm.rs b/crates/socket-patch-cli/tests/e2e_safety_pnpm.rs index 783e7337a..7af958619 100644 --- a/crates/socket-patch-cli/tests/e2e_safety_pnpm.rs +++ b/crates/socket-patch-cli/tests/e2e_safety_pnpm.rs @@ -293,11 +293,7 @@ fn apply_in_a_does_not_mutate_b_or_store() { }; // -- get + apply in proj_a only ---------------------------------- - assert_run_ok( - &fx.proj_a, - &["get", NPM_UUID, "--mode", "agent"], - "socket-patch get", - ); + assert_run_ok(&fx.proj_a, &["get", NPM_UUID, "--mode", "agent"], "socket-patch get"); // proj_a is patched. assert_eq!( @@ -401,11 +397,7 @@ fn pnpm_install_in_b_does_not_revert_a() { store_id }; - assert_run_ok( - &fx.proj_a, - &["get", NPM_UUID, "--mode", "agent"], - "socket-patch get", - ); + assert_run_ok(&fx.proj_a, &["get", NPM_UUID, "--mode", "agent"], "socket-patch get"); assert_eq!(git_sha256_file(&index_a), AFTER_HASH); // Re-run pnpm install in proj_b with frozen lockfile — this @@ -483,11 +475,7 @@ fn apply_in_pnpm_project_emits_layout_note() { let root = tempfile::tempdir().unwrap(); let fx = setup_two_pnpm_projects(root.path()); - let (_stdout, stderr) = assert_run_ok( - &fx.proj_a, - &["get", NPM_UUID, "--mode", "agent"], - "socket-patch get", - ); + let (_stdout, stderr) = assert_run_ok(&fx.proj_a, &["get", NPM_UUID, "--mode", "agent"], "socket-patch get"); // The exact phrasing is a stable contract. A bare `contains("pnpm")` // is worthless here — every pnpm store path printed on stderr diff --git a/crates/socket-patch-cli/tests/e2e_socket_yml_policy.rs b/crates/socket-patch-cli/tests/e2e_socket_yml_policy.rs index 9a02495b9..50018d6a9 100644 --- a/crates/socket-patch-cli/tests/e2e_socket_yml_policy.rs +++ b/crates/socket-patch-cli/tests/e2e_socket_yml_policy.rs @@ -61,11 +61,7 @@ impl Patch { "low" => 3, _ => 4, }; - self.severities - .iter() - .copied() - .min_by_key(|s| rank(s)) - .unwrap_or("unknown") + self.severities.iter().copied().min_by_key(|s| rank(s)).unwrap_or("unknown") } } @@ -204,9 +200,7 @@ async fn mount_api(server: &MockServer, patches: Vec) { .await; let detail_map = by_purl.clone(); Mock::given(method("GET")) - .and(path_regex(format!( - "^/v0/orgs/{ORG}/patches/by-package/.+$" - ))) + .and(path_regex(format!("^/v0/orgs/{ORG}/patches/by-package/.+$"))) .respond_with(move |req: &Request| { let raw = req.url.path().rsplit('/').next().unwrap(); let purl = percent_decode(raw); @@ -222,15 +216,11 @@ async fn mount_api(server: &MockServer, patches: Vec) { }) }) .collect(); - ResponseTemplate::new(200) - .set_body_json(json!({"patches": list, "canAccessPaidPatches": false})) + ResponseTemplate::new(200).set_body_json(json!({"patches": list, "canAccessPaidPatches": false})) }) .mount(server) .await; - let by_uuid: BTreeMap = patches - .iter() - .map(|p| (p.uuid.to_string(), p.clone())) - .collect(); + let by_uuid: BTreeMap = patches.iter().map(|p| (p.uuid.to_string(), p.clone())).collect(); let refs = by_uuid.clone(); Mock::given(method("POST")) .and(path(format!("/v0/orgs/{ORG}/patches/package"))) @@ -287,10 +277,8 @@ fn write_npm_root(dir: &Path, deps: &[&str]) { let dep_map: BTreeMap<&str, &str> = deps.iter().map(|d| (*d, "1.0.0")).collect(); std::fs::write( dir.join("package.json"), - serde_json::to_string_pretty( - &json!({"name": "consumer", "version": "0.0.0", "dependencies": dep_map}), - ) - .unwrap(), + serde_json::to_string_pretty(&json!({"name": "consumer", "version": "0.0.0", "dependencies": dep_map})) + .unwrap(), ) .unwrap(); let mut packages = serde_json::Map::new(); @@ -301,11 +289,7 @@ fn write_npm_root(dir: &Path, deps: &[&str]) { for name in deps { let pkg = dir.join("node_modules").join(name); std::fs::create_dir_all(&pkg).unwrap(); - std::fs::write( - pkg.join("package.json"), - format!(r#"{{ "name": "{name}", "version": "1.0.0" }}"#), - ) - .unwrap(); + std::fs::write(pkg.join("package.json"), format!(r#"{{ "name": "{name}", "version": "1.0.0" }}"#)).unwrap(); std::fs::write(pkg.join("index.js"), orig_index(name)).unwrap(); packages.insert( format!("node_modules/{name}"), @@ -320,20 +304,12 @@ fn write_npm_root(dir: &Path, deps: &[&str]) { "name": "consumer", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": packages }); - std::fs::write( - dir.join("package-lock.json"), - serde_json::to_string_pretty(&lock).unwrap() + "\n", - ) - .unwrap(); + std::fs::write(dir.join("package-lock.json"), serde_json::to_string_pretty(&lock).unwrap() + "\n").unwrap(); } fn write_gem(dir: &Path, name: &str, version: &str) { - std::fs::create_dir_all( - dir.join("vendor/bundle/ruby/3.0.0/gems") - .join(format!("{name}-{version}")) - .join("lib"), - ) - .unwrap(); + std::fs::create_dir_all(dir.join("vendor/bundle/ruby/3.0.0/gems").join(format!("{name}-{version}")).join("lib")) + .unwrap(); } /// The monorepo: `services/web` (alpha, beta, left-pad + a gem), @@ -373,11 +349,7 @@ impl Repo { if entry.file_type().unwrap().is_dir() { walk(&path, root, out); } else { - let rel = path - .strip_prefix(root) - .unwrap() - .to_string_lossy() - .into_owned(); + let rel = path.strip_prefix(root).unwrap().to_string_lossy().into_owned(); out.insert(rel, std::fs::read(&path).unwrap()); } } @@ -397,8 +369,7 @@ fn run_cli(cwd: &Path, args: &[&str], env: &[(&str, &str)]) -> (i32, String, Str cmd.env_remove(key); } } - cmd.env_remove("GIT_CEILING_DIRECTORIES") - .env_remove("VIRTUAL_ENV"); + cmd.env_remove("GIT_CEILING_DIRECTORIES").env_remove("VIRTUAL_ENV"); cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); // The fixture's hosted pins name this origin; it makes them recorded. cmd.env("SOCKET_PATCH_SERVER_URL", "http://patch.test"); @@ -412,8 +383,7 @@ fn run_cli(cwd: &Path, args: &[&str], env: &[(&str, &str)]) -> (i32, String, Str ] { cmd.env(var, &absent); } - cmd.env("NPM_CONFIG_ALLOW_REMOTE", "") - .env("npm_config_allow_remote", ""); + cmd.env("NPM_CONFIG_ALLOW_REMOTE", "").env("npm_config_allow_remote", ""); for (k, v) in env { cmd.env(k, v); } @@ -448,9 +418,8 @@ fn scan_json(cwd: &Path, api: &str, extra: &[&str], env: &[(&str, &str)]) -> (i3 let mut args = vec!["--json"]; args.extend_from_slice(extra); let (code, stdout, stderr) = scan(cwd, api, &args, env); - let doc: Value = serde_json::from_str(&stdout).unwrap_or_else(|e| { - panic!("stdout must be JSON ({e})\nstdout=\n{stdout}\nstderr=\n{stderr}") - }); + let doc: Value = serde_json::from_str(&stdout) + .unwrap_or_else(|e| panic!("stdout must be JSON ({e})\nstdout=\n{stdout}\nstderr=\n{stderr}")); (code, doc) } @@ -459,12 +428,7 @@ fn filtered(doc: &Value) -> Vec<(Option, String)> { .as_array() .unwrap() .iter() - .map(|f| { - ( - f["purl"].as_str().map(str::to_string), - f["reason"].as_str().unwrap().to_string(), - ) - }) + .map(|f| (f["purl"].as_str().map(str::to_string), f["reason"].as_str().unwrap().to_string())) .collect() } @@ -480,11 +444,7 @@ fn filtered_reason<'a>(doc: &'a Value, purl: &str) -> &'a Value { fn warning_codes(doc: &Value) -> Vec { doc["warnings"] .as_array() - .map(|w| { - w.iter() - .filter_map(|e| e["code"].as_str().map(str::to_string)) - .collect() - }) + .map(|w| w.iter().filter_map(|e| e["code"].as_str().map(str::to_string)).collect()) .unwrap_or_default() } @@ -504,28 +464,16 @@ async fn hosted_filters_by_ecosystem_package_and_severity() { assert_eq!(code, 0, "{doc:#}"); let lock = repo.lock("services/web"); - assert!( - lock.contains(&P_ALPHA.hosted_url()), - "alpha is patched:\n{lock}" - ); - assert!( - !lock.contains(P_BETA.uuid), - "beta is below the floor:\n{lock}" - ); - assert!( - !lock.contains(P_LEFTPAD.uuid), - "left-pad is ignored:\n{lock}" - ); + assert!(lock.contains(&P_ALPHA.hosted_url()), "alpha is patched:\n{lock}"); + assert!(!lock.contains(P_BETA.uuid), "beta is below the floor:\n{lock}"); + assert!(!lock.contains(P_LEFTPAD.uuid), "left-pad is ignored:\n{lock}"); let policy = &doc["policy"]; assert_eq!(policy["source"], "file"); assert_eq!(policy["path"], "socket.yml"); assert_eq!(policy["sha256"].as_str().unwrap().len(), 64); assert_eq!(policy["enabled"], true); - assert_eq!( - policy["minSeverity"], - json!({"value": "high", "source": "file"}) - ); + assert_eq!(policy["minSeverity"], json!({"value": "high", "source": "file"})); let beta = filtered_reason(&doc, "pkg:npm/beta@1.0.0"); assert_eq!(beta["reason"], "policy_severity"); assert_eq!(beta["detail"], "low < high"); @@ -533,15 +481,8 @@ async fn hosted_filters_by_ecosystem_package_and_severity() { assert_eq!(beta["project"], "services/web"); let left_pad = filtered_reason(&doc, "pkg:npm/left-pad@1.0.0"); assert_eq!(left_pad["reason"], "policy_package_ignored"); - assert_eq!( - left_pad["uuid"], - Value::Null, - "filtered before any patch lookup" - ); - assert_eq!( - left_pad["detail"], - "pkg:npm/left-pad (patches.ignorePackages)" - ); + assert_eq!(left_pad["uuid"], Value::Null, "filtered before any patch lookup"); + assert_eq!(left_pad["detail"], "pkg:npm/left-pad (patches.ignorePackages)"); let rack = filtered_reason(&doc, "pkg:gem/rack@1.0.0"); assert_eq!(rack["reason"], "policy_ecosystem"); assert_eq!(policy["counts"]["filtered"], 3); @@ -551,10 +492,7 @@ async fn hosted_filters_by_ecosystem_package_and_severity() { for r in &reqs { if r.url.path().ends_with("/patches/batch") { let body = String::from_utf8_lossy(&r.body); - assert!( - !body.contains("left-pad") && !body.contains("rack"), - "{body}" - ); + assert!(!body.contains("left-pad") && !body.contains("rack"), "{body}"); } } assert_eq!(doc["redirect"]["redirected"], 1, "{:#}", doc["redirect"]); @@ -565,27 +503,13 @@ async fn hosted_filters_by_ecosystem_package_and_severity() { async fn hosted_dry_run_makes_the_same_decisions_and_writes_nothing() { let server = MockServer::start().await; mount_api(&server, catalog()).await; - let repo = Repo::new(Some( - "version: 2\npatches:\n minSeverity: high\n ecosystems: [npm]\n", - )); + let repo = Repo::new(Some("version: 2\npatches:\n minSeverity: high\n ecosystems: [npm]\n")); let before = repo.snapshot(); - let (code, doc) = scan_json( - &repo.dir("services/web"), - &server.uri(), - &["--dry-run"], - &[], - ); + let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &["--dry-run"], &[]); assert_eq!(code, 0, "{doc:#}"); assert_eq!(repo.snapshot(), before, "a dry run changes no bytes"); - assert_eq!( - doc["redirect"]["redirected"], 2, - "alpha and left-pad: {:#}", - doc["redirect"] - ); - assert_eq!( - filtered_reason(&doc, "pkg:npm/beta@1.0.0")["reason"], - "policy_severity" - ); + assert_eq!(doc["redirect"]["redirected"], 2, "alpha and left-pad: {:#}", doc["redirect"]); + assert_eq!(filtered_reason(&doc, "pkg:npm/beta@1.0.0")["reason"], "policy_severity"); } #[tokio::test] @@ -593,24 +517,14 @@ async fn hosted_dry_run_makes_the_same_decisions_and_writes_nothing() { async fn path_globs_apply_default_ignores_and_ignore_paths_human() { let server = MockServer::start().await; mount_api(&server, catalog()).await; - let repo = Repo::new(Some( - "version: 2\npatches:\n ignorePaths: [\"/services/legacy/\"]\n", - )); + let repo = Repo::new(Some("version: 2\npatches:\n ignorePaths: [\"/services/legacy/\"]\n")); let legacy = repo.lock("services/legacy"); let test_lock = repo.lock("services/test"); let (code, stdout, stderr) = scan(&repo.root, &server.uri(), &["services/*"], &[]); assert_eq!(code, 0, "stdout:\n{stdout}\nstderr:\n{stderr}"); assert!(repo.lock("services/web").contains(&P_ALPHA.hosted_url())); - assert_eq!( - repo.lock("services/legacy"), - legacy, - "ignored by patches.ignorePaths" - ); - assert_eq!( - repo.lock("services/test"), - test_lock, - "a discovered test/ root is a built-in ignore" - ); + assert_eq!(repo.lock("services/legacy"), legacy, "ignored by patches.ignorePaths"); + assert_eq!(repo.lock("services/test"), test_lock, "a discovered test/ root is a built-in ignore"); assert!(stdout.contains("Policy (socket.yml)"), "{stdout}"); // Named literally, the test/ root is explicit: defaults do not apply. @@ -624,9 +538,7 @@ async fn path_globs_apply_default_ignores_and_ignore_paths_human() { async fn include_paths_limit_roots() { let server = MockServer::start().await; mount_api(&server, catalog()).await; - let repo = Repo::new(Some( - "version: 2\npatches:\n includePaths: [\"/services/legacy/\"]\n", - )); + let repo = Repo::new(Some("version: 2\npatches:\n includePaths: [\"/services/legacy/\"]\n")); let web = repo.lock("services/web"); let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &[], &[]); assert_eq!(code, 0, "{doc:#}"); @@ -659,10 +571,7 @@ async fn invalid_file_fails_closed_before_any_request_or_write() { assert!(message.contains("--no-socket-yml"), "{message}"); assert!(doc.get("policy").is_none()); assert_eq!(repo.snapshot(), before); - assert!( - server.received_requests().await.unwrap().is_empty(), - "no request before the policy loads" - ); + assert!(server.received_requests().await.unwrap().is_empty(), "no request before the policy loads"); // Human output names the code on stderr, same exit code. let (code, _, stderr) = scan(&repo.dir("services/web"), &server.uri(), &[], &[]); @@ -670,20 +579,10 @@ async fn invalid_file_fails_closed_before_any_request_or_write() { assert!(stderr.contains("socket_yml_invalid"), "{stderr}"); // --no-socket-yml (and its env var) skips the file. - let (code, doc) = scan_json( - &repo.dir("services/web"), - &server.uri(), - &["--no-socket-yml", "--dry-run"], - &[], - ); + let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &["--no-socket-yml", "--dry-run"], &[]); assert_eq!(code, 0, "{doc:#}"); assert_eq!(doc["policy"]["source"], "bypassed"); - let (code, doc) = scan_json( - &repo.dir("services/web"), - &server.uri(), - &["--dry-run"], - &[("SOCKET_NO_SOCKET_YML", "1")], - ); + let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &["--dry-run"], &[("SOCKET_NO_SOCKET_YML", "1")]); assert_eq!(code, 0, "{doc:#}"); assert_eq!(doc["policy"]["source"], "bypassed"); } @@ -694,11 +593,7 @@ async fn both_files_disagreeing_is_ambiguous() { let server = MockServer::start().await; mount_api(&server, catalog()).await; let repo = Repo::new(Some("version: 2\npatches:\n maxNewPatches: 1\n")); - std::fs::write( - repo.root.join("socket.yaml"), - "version: 2\npatches:\n maxNewPatches: 2\n", - ) - .unwrap(); + std::fs::write(repo.root.join("socket.yaml"), "version: 2\npatches:\n maxNewPatches: 2\n").unwrap(); let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &[], &[]); assert_eq!(code, 1); assert_eq!(doc["errorCode"], "socket_yml_ambiguous"); @@ -711,66 +606,26 @@ async fn severity_flag_and_env_override_the_file() { mount_api(&server, catalog()).await; let repo = Repo::new(Some("version: 2\npatches:\n minSeverity: high\n")); let web = repo.dir("services/web"); - let (code, doc) = scan_json( - &web, - &server.uri(), - &["--dry-run", "--min-severity", "none"], - &[], - ); + let (code, doc) = scan_json(&web, &server.uri(), &["--dry-run", "--min-severity", "none"], &[]); assert_eq!(code, 0, "{doc:#}"); - assert_eq!( - doc["policy"]["minSeverity"], - json!({"value": null, "source": "flag"}) - ); - assert_eq!( - doc["redirect"]["redirected"], 3, - "beta too once the floor is lifted" - ); + assert_eq!(doc["policy"]["minSeverity"], json!({"value": null, "source": "flag"})); + assert_eq!(doc["redirect"]["redirected"], 3, "beta too once the floor is lifted"); - let (code, doc) = scan_json( - &web, - &server.uri(), - &["--dry-run"], - &[("SOCKET_MIN_SEVERITY", "critical")], - ); + let (code, doc) = scan_json(&web, &server.uri(), &["--dry-run"], &[("SOCKET_MIN_SEVERITY", "critical")]); assert_eq!(code, 0, "{doc:#}"); - assert_eq!( - doc["policy"]["minSeverity"], - json!({"value": "critical", "source": "env"}) - ); + assert_eq!(doc["policy"]["minSeverity"], json!({"value": "critical", "source": "env"})); assert_eq!(doc["redirect"]["redirected"], 1); // The flag beats the env; an empty env value is unset. - let (_, doc) = scan_json( - &web, - &server.uri(), - &["--dry-run", "--min-severity", "moderate"], - &[("SOCKET_MIN_SEVERITY", "critical")], - ); - assert_eq!( - doc["policy"]["minSeverity"], - json!({"value": "medium", "source": "flag"}) - ); - let (_, doc) = scan_json( - &web, - &server.uri(), - &["--dry-run"], - &[("SOCKET_MIN_SEVERITY", "")], - ); - assert_eq!( - doc["policy"]["minSeverity"], - json!({"value": "high", "source": "file"}) - ); + let (_, doc) = scan_json(&web, &server.uri(), &["--dry-run", "--min-severity", "moderate"], &[("SOCKET_MIN_SEVERITY", "critical")]); + assert_eq!(doc["policy"]["minSeverity"], json!({"value": "medium", "source": "flag"})); + let (_, doc) = scan_json(&web, &server.uri(), &["--dry-run"], &[("SOCKET_MIN_SEVERITY", "")]); + assert_eq!(doc["policy"]["minSeverity"], json!({"value": "high", "source": "file"})); // Malformed values are usage errors. let (code, _, stderr) = scan(&web, &server.uri(), &["--min-severity", "severe"], &[]); assert_eq!(code, 2, "{stderr}"); - let (code, _, stderr) = scan( - &web, - &server.uri(), - &[], - &[("SOCKET_MIN_SEVERITY", "severe")], - ); + let (code, _, stderr) = scan(&web, &server.uri(), &[], &[("SOCKET_MIN_SEVERITY", "severe")]); assert_eq!(code, 2, "{stderr}"); assert!(stderr.contains("SOCKET_MIN_SEVERITY"), "{stderr}"); } @@ -788,20 +643,12 @@ async fn narrowing_after_a_hosted_patch_leaves_the_pin_byte_identical() { assert!(pinned.contains(&P_ALPHA.hosted_url())); // A newer merged patch appears, and the repo now ignores alpha. - std::fs::write( - repo.root.join("socket.yml"), - "version: 2\npatches:\n ignorePackages: [alpha]\n", - ) - .unwrap(); + std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n ignorePackages: [alpha]\n").unwrap(); server.reset().await; mount_api(&server, vec![P_ALPHA, P_ALPHA_MERGED_NEW]).await; let (code, doc) = scan_json(&web, &server.uri(), &[], &[]); assert_eq!(code, 0, "{doc:#}"); - assert_eq!( - repo.lock("services/web"), - pinned, - "retained: not upgraded, not removed" - ); + assert_eq!(repo.lock("services/web"), pinned, "retained: not upgraded, not removed"); let retained = &doc["policy"]["retained"][0]; assert_eq!(retained["purl"], "pkg:npm/alpha@1.0.0"); assert_eq!(retained["recordedUuid"], P_ALPHA.uuid); @@ -819,11 +666,7 @@ async fn narrowing_after_a_hosted_patch_leaves_the_pin_byte_identical() { let (code, doc) = scan_json(&web, &server.uri(), &[], &[]); assert_eq!(code, 0, "{doc:#}"); assert_eq!(repo.lock("services/web"), pinned, "{yml}"); - assert_eq!( - doc["policy"]["retained"][0]["purl"], "pkg:npm/alpha@1.0.0", - "{yml}: {:#}", - doc["policy"] - ); + assert_eq!(doc["policy"]["retained"][0]["purl"], "pkg:npm/alpha@1.0.0", "{yml}: {:#}", doc["policy"]); } } @@ -838,15 +681,9 @@ async fn enabled_false_reports_and_writes_nothing() { assert_eq!(code, 0, "{doc:#}"); assert_eq!(repo.snapshot(), before); assert_eq!(doc["policy"]["enabled"], false); - assert!( - warning_codes(&doc).contains(&"patches_disabled".to_string()), - "{doc:#}" - ); + assert!(warning_codes(&doc).contains(&"patches_disabled".to_string()), "{doc:#}"); let reasons: Vec = filtered(&doc).into_iter().map(|(_, r)| r).collect(); - assert!( - !reasons.is_empty() && reasons.iter().all(|r| r == "policy_disabled"), - "{reasons:?}" - ); + assert!(!reasons.is_empty() && reasons.iter().all(|r| r == "policy_disabled"), "{reasons:?}"); assert_eq!(doc["redirect"]["redirected"], 0); } @@ -855,9 +692,7 @@ async fn enabled_false_reports_and_writes_nothing() { async fn report_only_json_fails_when_every_detail_query_fails() { let server = MockServer::start().await; Mock::given(method("GET")) - .and(path_regex(format!( - "^/v0/orgs/{ORG}/patches/by-package/.+$" - ))) + .and(path_regex(format!("^/v0/orgs/{ORG}/patches/by-package/.+$"))) .respond_with(ResponseTemplate::new(500)) .with_priority(1) .mount(&server) @@ -870,10 +705,7 @@ async fn report_only_json_fails_when_every_detail_query_fails() { assert_eq!(code, 1, "{doc:#}"); assert_eq!(doc["status"], "error", "{doc:#}"); assert!( - doc["error"] - .as_str() - .unwrap_or_default() - .contains("patch-detail queries failed"), + doc["error"].as_str().unwrap_or_default().contains("patch-detail queries failed"), "{doc:#}" ); assert_eq!(repo.snapshot(), before); @@ -894,11 +726,7 @@ async fn recorded_merge_below_the_floor_is_kept_until_a_more_severe_patch_is_ava "the only available patch is pinned:\n{pinned}" ); - std::fs::write( - repo.root.join("socket.yml"), - "version: 2\npatches:\n minSeverity: high\n", - ) - .unwrap(); + std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n minSeverity: high\n").unwrap(); let (code, doc) = scan_json(&web, &server.uri(), &[], &[]); assert_eq!(code, 0, "{doc:#}"); assert_eq!( @@ -950,17 +778,11 @@ async fn floor_with_nothing_admitted_reports_the_withheld_patch() { let (code, stdout, stderr) = scan(&web, &server.uri(), &[], &[]); assert_eq!(code, 0, "{stdout}\n{stderr}"); assert_eq!(repo.lock("services/web"), lock); - assert!( - stdout.contains("Policy (socket.yml): 1 skipped by filters"), - "{stdout}" - ); + assert!(stdout.contains("Policy (socket.yml): 1 skipped by filters"), "{stdout}"); // Only critical/high are named without --verbose. assert!(!stdout.contains("skipped beta"), "{stdout}"); let (_, stdout, _) = scan(&web, &server.uri(), &["--verbose"], &[]); - assert!( - stdout.contains("skipped pkg:npm/beta@1.0.0 (low): low < critical"), - "{stdout}" - ); + assert!(stdout.contains("skipped pkg:npm/beta@1.0.0 (low): low < critical"), "{stdout}"); } #[tokio::test] @@ -971,17 +793,9 @@ async fn path_outside_the_repo_is_a_usage_error() { let repo = Repo::new(None); let outside = repo.root.parent().unwrap().join("elsewhere"); write_npm_root(&outside, &["alpha"]); - let (code, _, stderr) = scan( - &repo.dir("services"), - &server.uri(), - &["web", "../../elsewhere"], - &[], - ); + let (code, _, stderr) = scan(&repo.dir("services"), &server.uri(), &["web", "../../elsewhere"], &[]); assert_eq!(code, 2, "{stderr}"); - assert!( - stderr.contains("is outside") && stderr.contains("run one scan per repository"), - "{stderr}" - ); + assert!(stderr.contains("is outside") && stderr.contains("run one scan per repository"), "{stderr}"); } #[tokio::test] @@ -989,9 +803,7 @@ async fn path_outside_the_repo_is_a_usage_error() { async fn project_ignore_paths_is_honored_without_a_patches_block() { let server = MockServer::start().await; mount_api(&server, catalog()).await; - let repo = Repo::new(Some( - "version: 2\nprojectIgnorePaths:\n - \"services/legacy/**\"\n", - )); + let repo = Repo::new(Some("version: 2\nprojectIgnorePaths:\n - \"services/legacy/**\"\n")); let legacy = repo.lock("services/legacy"); let (code, doc) = scan_json(&repo.dir("services/legacy"), &server.uri(), &[], &[]); assert_eq!(code, 0, "{doc:#}"); @@ -1001,22 +813,10 @@ async fn project_ignore_paths_is_honored_without_a_patches_block() { assert_eq!(entry["detail"], "services/legacy/** (projectIgnorePaths)"); // A malformed projectIgnorePaths without a patches block only warns. - std::fs::write( - repo.root.join("socket.yml"), - "version: 2\nprojectIgnorePaths: {a: 1}\n", - ) - .unwrap(); - let (code, doc) = scan_json( - &repo.dir("services/legacy"), - &server.uri(), - &["--dry-run"], - &[], - ); + std::fs::write(repo.root.join("socket.yml"), "version: 2\nprojectIgnorePaths: {a: 1}\n").unwrap(); + let (code, doc) = scan_json(&repo.dir("services/legacy"), &server.uri(), &["--dry-run"], &[]); assert_eq!(code, 0, "{doc:#}"); - assert!( - warning_codes(&doc).contains(&"socket_yml_ignored_value".to_string()), - "{doc:#}" - ); + assert!(warning_codes(&doc).contains(&"socket_yml_ignored_value".to_string()), "{doc:#}"); } // --------------------------------------------------------------------------- @@ -1045,18 +845,14 @@ async fn agent_mode_applies_only_admitted_patches() { let (code, doc) = scan_json(&web, &server.uri(), &["--mode", "agent"], &[]); assert_eq!(code, 0, "{doc:#}"); let manifest: Value = - serde_json::from_str(&std::fs::read_to_string(web.join(".socket/manifest.json")).unwrap()) - .unwrap(); + serde_json::from_str(&std::fs::read_to_string(web.join(".socket/manifest.json")).unwrap()).unwrap(); let keys: Vec<&String> = manifest["patches"].as_object().unwrap().keys().collect(); assert_eq!(keys, ["pkg:npm/alpha@1.0.0"]); assert_eq!( std::fs::read_to_string(web.join("node_modules/alpha/index.js")).unwrap(), patched_index("alpha") ); - assert_eq!( - std::fs::read_to_string(web.join("node_modules/beta/index.js")).unwrap(), - orig_index("beta") - ); + assert_eq!(std::fs::read_to_string(web.join("node_modules/beta/index.js")).unwrap(), orig_index("beta")); } #[tokio::test] @@ -1071,23 +867,13 @@ async fn agent_mode_retains_a_recorded_patch_the_policy_now_excludes() { let manifest_before = std::fs::read(web.join(".socket/manifest.json")).unwrap(); let installed_before = std::fs::read(web.join("node_modules/alpha/index.js")).unwrap(); - std::fs::write( - repo.root.join("socket.yml"), - "version: 2\npatches:\n ecosystems: [pypi]\n", - ) - .unwrap(); + std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n ecosystems: [pypi]\n").unwrap(); server.reset().await; mount_api(&server, vec![P_ALPHA, P_ALPHA_MERGED_NEW]).await; let (code, doc) = scan_json(&web, &server.uri(), &["--mode", "agent"], &[]); assert_eq!(code, 0, "{doc:#}"); - assert_eq!( - std::fs::read(web.join(".socket/manifest.json")).unwrap(), - manifest_before - ); - assert_eq!( - std::fs::read(web.join("node_modules/alpha/index.js")).unwrap(), - installed_before - ); + assert_eq!(std::fs::read(web.join(".socket/manifest.json")).unwrap(), manifest_before); + assert_eq!(std::fs::read(web.join("node_modules/alpha/index.js")).unwrap(), installed_before); assert_eq!(doc["policy"]["retained"][0]["reason"], "policy_ecosystem"); assert_eq!(doc["policy"]["retained"][0]["upgradeAvailable"], true); } @@ -1103,12 +889,7 @@ async fn vendored_dry_run_previews_only_admitted_patches() { mount_api(&server, catalog()).await; let repo = Repo::new(Some("version: 2\npatches:\n packages: [\"pkg:npm/beta\", \"pkg:npm/left-pad\"]\n minSeverity: medium\n")); let before = repo.snapshot(); - let (code, doc) = scan_json( - &repo.dir("services/web"), - &server.uri(), - &["--mode", "vendored", "--dry-run"], - &[], - ); + let (code, doc) = scan_json(&repo.dir("services/web"), &server.uri(), &["--mode", "vendored", "--dry-run"], &[]); assert_eq!(code, 0, "{doc:#}"); assert_eq!(repo.snapshot(), before); let previewed: Vec<&str> = doc["vendor"]["patches"] @@ -1118,14 +899,8 @@ async fn vendored_dry_run_previews_only_admitted_patches() { .filter_map(|p| p["purl"].as_str()) .collect(); assert_eq!(previewed, ["pkg:npm/left-pad@1.0.0"], "{doc:#}"); - assert_eq!( - filtered_reason(&doc, "pkg:npm/alpha@1.0.0")["reason"], - "policy_package_not_listed" - ); - assert_eq!( - filtered_reason(&doc, "pkg:npm/beta@1.0.0")["reason"], - "policy_severity" - ); + assert_eq!(filtered_reason(&doc, "pkg:npm/alpha@1.0.0")["reason"], "policy_package_not_listed"); + assert_eq!(filtered_reason(&doc, "pkg:npm/beta@1.0.0")["reason"], "policy_severity"); } // --------------------------------------------------------------------------- @@ -1157,16 +932,9 @@ async fn get_bypasses_the_policy_with_a_warning() { let (code, stdout, stderr) = run_cli(&web, &args, &[]); assert_eq!(code, 0, "stdout:\n{stdout}\nstderr:\n{stderr}"); let doc: Value = serde_json::from_str(&stdout).unwrap(); - let warnings: Vec<&str> = doc["warnings"] - .as_array() - .unwrap() - .iter() - .filter_map(Value::as_str) - .collect(); + let warnings: Vec<&str> = doc["warnings"].as_array().unwrap().iter().filter_map(Value::as_str).collect(); assert!( - warnings - .iter() - .any(|w| w.starts_with("(policy_bypassed)") && w.contains("alpha")), + warnings.iter().any(|w| w.starts_with("(policy_bypassed)") && w.contains("alpha")), "{doc:#}" ); @@ -1192,65 +960,30 @@ async fn agent_mode_honors_path_filters_and_keeps_the_prune_universe() { // The root is excluded by path: nothing selected, and a --sync (agent // + prune) still judges the full crawl, so no entry is pruned. - std::fs::write( - repo.root.join("socket.yml"), - "version: 2\npatches:\n includePaths: [\"/services/legacy/\"]\n", - ) - .unwrap(); + std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n includePaths: [\"/services/legacy/\"]\n").unwrap(); let (code, doc) = scan_json(&web, &server.uri(), &["--sync"], &[]); assert_eq!(code, 0, "{doc:#}"); - assert_eq!( - std::fs::read(web.join(".socket/manifest.json")).unwrap(), - manifest_before - ); + assert_eq!(std::fs::read(web.join(".socket/manifest.json")).unwrap(), manifest_before); assert_eq!(doc["policy"]["filtered"][0]["purl"], Value::Null); - assert_eq!( - doc["policy"]["filtered"][0]["reason"], - "policy_path_not_included" - ); - assert_eq!( - doc["policy"]["counts"]["retained"], 2, - "{:#}", - doc["policy"] - ); - assert_eq!( - doc["gc"]["removed"].as_array().map_or(0, Vec::len), - 0, - "{:#}", - doc["gc"] - ); + assert_eq!(doc["policy"]["filtered"][0]["reason"], "policy_path_not_included"); + assert_eq!(doc["policy"]["counts"]["retained"], 2, "{:#}", doc["policy"]); + assert_eq!(doc["gc"]["removed"].as_array().map_or(0, Vec::len), 0, "{:#}", doc["gc"]); // A narrower ecosystem list under --sync prunes nothing either. - std::fs::write( - repo.root.join("socket.yml"), - "version: 2\npatches:\n ecosystems: [pypi]\n", - ) - .unwrap(); + std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n ecosystems: [pypi]\n").unwrap(); let (code, doc) = scan_json(&web, &server.uri(), &["--sync"], &[]); assert_eq!(code, 0, "{doc:#}"); - assert_eq!( - std::fs::read(web.join(".socket/manifest.json")).unwrap(), - manifest_before - ); + assert_eq!(std::fs::read(web.join(".socket/manifest.json")).unwrap(), manifest_before); // patches.enabled: false skips the GC entirely. std::fs::remove_dir_all(web.join("node_modules/beta")).unwrap(); - let pkg_lock = repo - .lock("services/web") - .replace("\"node_modules/beta\"", "\"node_modules/gone\""); + let pkg_lock = repo.lock("services/web").replace("\"node_modules/beta\"", "\"node_modules/gone\""); std::fs::write(web.join("package-lock.json"), pkg_lock).unwrap(); - std::fs::write( - repo.root.join("socket.yml"), - "version: 2\npatches:\n enabled: false\n", - ) - .unwrap(); + std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n enabled: false\n").unwrap(); let (code, doc) = scan_json(&web, &server.uri(), &["--sync"], &[]); assert_eq!(code, 0, "{doc:#}"); assert!(doc.get("gc").is_none(), "{doc:#}"); - assert_eq!( - std::fs::read(web.join(".socket/manifest.json")).unwrap(), - manifest_before - ); + assert_eq!(std::fs::read(web.join(".socket/manifest.json")).unwrap(), manifest_before); } #[tokio::test] @@ -1264,53 +997,29 @@ async fn narrowing_after_vendoring_leaves_the_vendored_package_byte_identical() let before = compute_git_sha256_from_bytes(orig_index("alpha").as_bytes()); let after = compute_git_sha256_from_bytes(patched_index("alpha").as_bytes()); std::fs::create_dir_all(web.join(".socket/blobs")).unwrap(); - std::fs::write( - web.join(".socket/blobs").join(&after), - patched_index("alpha"), - ) - .unwrap(); + std::fs::write(web.join(".socket/blobs").join(&after), patched_index("alpha")).unwrap(); let manifest = json!({"patches": {P_ALPHA.purl(): { "uuid": P_ALPHA.uuid, "exportedAt": "2026-01-01T00:00:00Z", "files": {"package/index.js": {"beforeHash": before, "afterHash": after}}, "vulnerabilities": {}, "description": "d", "license": "MIT", "tier": "free" }}}); - std::fs::write( - web.join(".socket/manifest.json"), - serde_json::to_vec_pretty(&manifest).unwrap(), - ) - .unwrap(); + std::fs::write(web.join(".socket/manifest.json"), serde_json::to_vec_pretty(&manifest).unwrap()).unwrap(); let fixture = prebuilt_common::Server::project(&web); let (code, stdout, stderr) = run_cli( &web, &["vendor", "--json", "--cwd", web.to_str().unwrap()], - &[ - ("SOCKET_VENDOR_URL", &fixture.uri), - ("SOCKET_PATCH_SERVER_URL", &fixture.uri), - ], + &[("SOCKET_VENDOR_URL", &fixture.uri), ("SOCKET_PATCH_SERVER_URL", &fixture.uri)], ); assert_eq!(code, 0, "vendor fixture: {stdout}\n{stderr}"); - assert!( - repo.lock("services/web").contains(".socket/vendor/"), - "vendored lock" - ); + assert!(repo.lock("services/web").contains(".socket/vendor/"), "vendored lock"); let snapshot = repo.snapshot(); - std::fs::write( - repo.root.join("socket.yml"), - "version: 2\npatches:\n ignorePackages: [\"pkg:npm/alpha\"]\n", - ) - .unwrap(); + std::fs::write(repo.root.join("socket.yml"), "version: 2\npatches:\n ignorePackages: [\"pkg:npm/alpha\"]\n").unwrap(); let (code, doc) = scan_json(&web, &server.uri(), &["--mode", "vendored"], &[]); assert_eq!(code, 0, "{doc:#}"); let mut after_scan = repo.snapshot(); after_scan.remove("socket.yml"); - assert_eq!( - after_scan, snapshot, - "the vendored package, its lock wiring and ledger stay byte-identical" - ); - assert_eq!( - doc["policy"]["retained"][0]["purl"], "pkg:npm/alpha@1.0.0", - "{:#}", - doc["policy"] - ); + assert_eq!(after_scan, snapshot, "the vendored package, its lock wiring and ledger stay byte-identical"); + assert_eq!(doc["policy"]["retained"][0]["purl"], "pkg:npm/alpha@1.0.0", "{:#}", doc["policy"]); } + diff --git a/crates/socket-patch-cli/tests/e2e_vex_lockfile/common_selftest.rs b/crates/socket-patch-cli/tests/e2e_vex_lockfile/common_selftest.rs index 0dd0998af..83a8de749 100644 --- a/crates/socket-patch-cli/tests/e2e_vex_lockfile/common_selftest.rs +++ b/crates/socket-patch-cli/tests/e2e_vex_lockfile/common_selftest.rs @@ -152,11 +152,7 @@ fn committed_pre_v5_ledger_lets_a_hosted_pin_attest_offline() { ); } api.assert_no_requests(); - assert_eq!( - std::fs::read(&ledger).unwrap(), - before, - "vex never rewrites it" - ); + assert_eq!(std::fs::read(&ledger).unwrap(), before, "vex never rewrites it"); let other = "0b0b0b0b-0b0b-4b0b-8b0b-0b0b0b0b0b0b"; let mut stale = left_pad_view(); diff --git a/crates/socket-patch-cli/tests/e2e_yarn4_pnpm_linker_build.rs b/crates/socket-patch-cli/tests/e2e_yarn4_pnpm_linker_build.rs index ddadbb45d..ce5d1144b 100644 --- a/crates/socket-patch-cli/tests/e2e_yarn4_pnpm_linker_build.rs +++ b/crates/socket-patch-cli/tests/e2e_yarn4_pnpm_linker_build.rs @@ -571,9 +571,9 @@ async fn yarn4_pnpm_linker_hosted_redirect_fresh_checkout_installs_patched_bytes let root_pkg = std::fs::read_to_string(proj.join("package.json")).unwrap(); let root_pkg: serde_json::Value = serde_json::from_str(&root_pkg).unwrap(); assert!( - root_pkg["resolutions"].as_object().is_some_and(|r| r - .iter() - .any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) + root_pkg["resolutions"] + .as_object() + .is_some_and(|r| r.iter().any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) && v.as_str() == Some(hosted_url.as_str()))), "package.json must route {DEP} to the hosted tarball: {root_pkg}" ); @@ -596,10 +596,7 @@ async fn yarn4_pnpm_linker_hosted_redirect_fresh_checkout_installs_patched_bytes serde_json::from_slice(&std::fs::read(proj.join("package.json")).unwrap()).unwrap(); let before: serde_json::Value = serde_json::from_slice(&pkg_before).unwrap(); after.as_object_mut().unwrap().shift_remove("resolutions"); - assert_eq!( - after, before, - "the hosted pin only adds `resolutions` to package.json" - ); + assert_eq!(after, before, "the hosted pin only adds `resolutions` to package.json"); } eprintln!("HOSTED REWIRE OK"); @@ -628,10 +625,7 @@ async fn yarn4_pnpm_linker_hosted_redirect_fresh_checkout_installs_patched_bytes eprintln!("FRESH INSTALL + YARN NODE RESOLUTION OK"); // MANIFEST-LESS VEX over the hosted wiring (see `yarn_berry_common`). - let registry_state = [ - ("yarn.lock", registry_lock), - ("package.json", pkg_before.clone()), - ]; + let registry_state = [("yarn.lock", registry_lock), ("package.json", pkg_before.clone())]; let yarn = |cwd: &Path, args: &[&str], env: &[(&str, &str)]| corepack(cwd, yarn_berry(), args, env); let api_url = server.uri(); diff --git a/crates/socket-patch-cli/tests/e2e_yarn4_workspaces_build.rs b/crates/socket-patch-cli/tests/e2e_yarn4_workspaces_build.rs index 2794a4781..d2aec0078 100644 --- a/crates/socket-patch-cli/tests/e2e_yarn4_workspaces_build.rs +++ b/crates/socket-patch-cli/tests/e2e_yarn4_workspaces_build.rs @@ -566,9 +566,9 @@ async fn yarn4_workspaces_hosted_redirect_rewires_member_dep_from_root_scan() { let root_pkg = std::fs::read_to_string(proj.join("package.json")).unwrap(); let root_pkg: serde_json::Value = serde_json::from_str(&root_pkg).unwrap(); assert!( - root_pkg["resolutions"].as_object().is_some_and(|r| r - .iter() - .any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) + root_pkg["resolutions"] + .as_object() + .is_some_and(|r| r.iter().any(|(sel, v)| sel.starts_with(&format!("{DEP}@npm:")) && v.as_str() == Some(hosted_url.as_str()))), "package.json must route {DEP} to the hosted tarball: {root_pkg}" ); @@ -596,10 +596,7 @@ async fn yarn4_workspaces_hosted_redirect_rewires_member_dep_from_root_scan() { serde_json::from_slice(&std::fs::read(proj.join("package.json")).unwrap()).unwrap(); let before: serde_json::Value = serde_json::from_slice(&root_pkg_before).unwrap(); after.as_object_mut().unwrap().shift_remove("resolutions"); - assert_eq!( - after, before, - "the hosted pin only adds `resolutions` to the root package.json" - ); + assert_eq!(after, before, "the hosted pin only adds `resolutions` to the root package.json"); } assert_eq!( std::fs::read(proj.join("packages/app/package.json")).unwrap(), @@ -633,10 +630,7 @@ async fn yarn4_workspaces_hosted_redirect_rewires_member_dep_from_root_scan() { eprintln!("FRESH INSTALL + MEMBER RESOLUTION OK"); // MANIFEST-LESS VEX over the hosted wiring (see `yarn_berry_common`). - let registry_state = [ - ("yarn.lock", registry_lock), - ("package.json", root_pkg_before.clone()), - ]; + let registry_state = [("yarn.lock", registry_lock), ("package.json", root_pkg_before.clone())]; let yarn = |cwd: &Path, args: &[&str], env: &[(&str, &str)]| corepack(cwd, yarn_berry(), args, env); let api_url = server.uri(); diff --git a/crates/socket-patch-cli/tests/get/get_edge_cases_e2e.rs b/crates/socket-patch-cli/tests/get/get_edge_cases_e2e.rs index 6cdd44ef1..e32b4ea97 100644 --- a/crates/socket-patch-cli/tests/get/get_edge_cases_e2e.rs +++ b/crates/socket-patch-cli/tests/get/get_edge_cases_e2e.rs @@ -469,16 +469,8 @@ fn get_help_lists_all_identifier_flags() { ); } // Help text is for users: no implementation notes from the source. - for leak in [ - "value_parser", - "parse_bool_flag", - "No env binding", - "locally- installed", - ] { - assert!( - !stdout.contains(leak), - "get --help leaks {leak:?}: {stdout}" - ); + for leak in ["value_parser", "parse_bool_flag", "No env binding", "locally- installed"] { + assert!(!stdout.contains(leak), "get --help leaks {leak:?}: {stdout}"); } } diff --git a/crates/socket-patch-cli/tests/get/global_packages_e2e.rs b/crates/socket-patch-cli/tests/get/global_packages_e2e.rs index a86958fe8..25bdadd21 100644 --- a/crates/socket-patch-cli/tests/get/global_packages_e2e.rs +++ b/crates/socket-patch-cli/tests/get/global_packages_e2e.rs @@ -211,10 +211,7 @@ fn assert_rollback_noop(stdout: &str) { r["skipped"], "package_not_installed", "a no-op rollback may carry only not-installed markers; envelope={v}" ); - assert!( - r["path"].is_null(), - "marker path must be null; envelope={v}" - ); + assert!(r["path"].is_null(), "marker path must be null; envelope={v}"); assert!( r.get("success").is_none() && r.get("error").is_none(), "markers carry no success/error keys; envelope={v}" diff --git a/crates/socket-patch-cli/tests/help_text_hygiene.rs b/crates/socket-patch-cli/tests/help_text_hygiene.rs index 467ed46c5..9b3280e8a 100644 --- a/crates/socket-patch-cli/tests/help_text_hygiene.rs +++ b/crates/socket-patch-cli/tests/help_text_hygiene.rs @@ -61,11 +61,7 @@ fn every_help_page_has_no_developer_notes() { names.extend(cmd.get_subcommands().map(|s| s.get_name().to_string())); let mut failures = Vec::new(); for name in &names { - let path: Vec<&str> = if name.is_empty() { - vec![] - } else { - vec![name.as_str()] - }; + let path: Vec<&str> = if name.is_empty() { vec![] } else { vec![name.as_str()] }; let text = long_help(&path); let found = leaks(&text); if !found.is_empty() { @@ -151,9 +147,7 @@ fn vex_product_list_renders_one_item_per_line() { fn root_command_list_uses_the_verb_form() { let text = long_help(&[]); assert!( - text.contains( - "Undo patches: restore original files and unwind hosted or vendored lockfile wiring" - ), + text.contains("Undo patches: restore original files and unwind hosted or vendored lockfile wiring"), "{text}" ); assert!(!text.contains("Rollback patches"), "{text}"); @@ -264,24 +258,11 @@ fn short_help_lists_about_eight_options_and_long_help_lists_all() { .filter(|l| l.starts_with('-') && !l.starts_with("-h,") && !l.starts_with("-V,")) .count() }; - assert!( - count(&short) <= 9, - "{name} -h lists {} options:\n{short}", - count(&short) - ); - assert!( - count(&long) > count(&short), - "{name} --help must list more than -h" - ); - assert!( - short.contains("--json") && short.contains("--cwd"), - "{name}" - ); + assert!(count(&short) <= 9, "{name} -h lists {} options:\n{short}", count(&short)); + assert!(count(&long) > count(&short), "{name} --help must list more than -h"); + assert!(short.contains("--json") && short.contains("--cwd"), "{name}"); } let scan = cmd.find_subcommand_mut("scan").expect("scan"); let long = scan.render_long_help().to_string(); - assert!( - !long.contains("--apply") && !long.contains("--vendor "), - "{long}" - ); + assert!(!long.contains("--apply") && !long.contains("--vendor "), "{long}"); } diff --git a/crates/socket-patch-cli/tests/hosted_memory_engine.rs b/crates/socket-patch-cli/tests/hosted_memory_engine.rs index 54826f34d..a340abb23 100644 --- a/crates/socket-patch-cli/tests/hosted_memory_engine.rs +++ b/crates/socket-patch-cli/tests/hosted_memory_engine.rs @@ -984,8 +984,7 @@ async fn a_vlt_project_is_withheld_as_offline() { .and_then(|w| w["detail"].as_str()) .expect("the preflight warning is reported"); assert!( - detail.contains("/patch/npm//") - && detail.contains(": offline; nothing was written"), + detail.contains("/patch/npm//") && detail.contains(": offline; nothing was written"), "the offline refusal quotes the redacted URL" ); assert!(output.changed_files.is_empty()); diff --git a/crates/socket-patch-cli/tests/hosted_memory_parity.rs b/crates/socket-patch-cli/tests/hosted_memory_parity.rs index 0af392eb4..b297eaf79 100644 --- a/crates/socket-patch-cli/tests/hosted_memory_parity.rs +++ b/crates/socket-patch-cli/tests/hosted_memory_parity.rs @@ -754,11 +754,7 @@ fn policy_repo(socket_yml: &str) -> (Vec, BTreeMap>) { let mut patches = patches_from_overrides(&npm.join("overrides.json"), None); patches.extend(patches_from_overrides(&cargo.join("overrides.json"), None)); let mut repo: BTreeMap> = BTreeMap::new(); - for (root, dir) in [ - ("apps/web", &npm), - ("apps/legacy", &npm), - ("services/api", &cargo), - ] { + for (root, dir) in [("apps/web", &npm), ("apps/legacy", &npm), ("services/api", &cargo)] { for (rel, bytes) in fixture_files(&dir.join("input")) { repo.insert(format!("{root}/{rel}"), bytes); } @@ -777,9 +773,7 @@ fn two_phase( socket_patch_cli::hosted_memory::PathSelection, socket_patch_cli::hosted_memory::HostedScanInput, ) { - use socket_patch_cli::hosted_memory::{ - select_paths, PolicyFileInput, SelectOptions, TreeEntryInput, - }; + use socket_patch_cli::hosted_memory::{select_paths, PolicyFileInput, SelectOptions, TreeEntryInput}; let entries: Vec = files .iter() .map(|(p, bytes)| TreeEntryInput { @@ -820,23 +814,15 @@ fn two_phase( (selection, input) } -fn policy_input( - files: &BTreeMap>, -) -> socket_patch_cli::hosted_memory::HostedScanInput { +fn policy_input(files: &BTreeMap>) -> socket_patch_cli::hosted_memory::HostedScanInput { let (selection, input) = two_phase(files, options(false)); - assert!( - selection.policy_error.is_none(), - "{:?}", - selection.policy_error - ); + assert!(selection.policy_error.is_none(), "{:?}", selection.policy_error); input } /// Session options as selection of `files` would hand them over, without /// going through selection (for inputs a host may get wrong). -fn policy_options( - files: &BTreeMap>, -) -> socket_patch_cli::hosted_memory::HostedScanOptions { +fn policy_options(files: &BTreeMap>) -> socket_patch_cli::hosted_memory::HostedScanOptions { let (selection, _) = two_phase(files, options(false)); let mut opts = options(false); opts.policy_paths = Some(selection.policy_paths); @@ -868,44 +854,25 @@ async fn parity_socket_yml_filters_the_same_roots_and_packages() { let server = MockServer::start().await; mount_api(&server, &patches).await; let (selection, input) = two_phase(&repo, options(false)); - assert!( - selection.policy_error.is_none(), - "{:?}", - selection.policy_error - ); + assert!(selection.policy_error.is_none(), "{:?}", selection.policy_error); let memory = run_engine(&server, input).await; assert!(memory.policy_error.is_none(), "{:?}", memory.policy_error); let roots: Vec<&str> = memory.projects.iter().map(|p| p.root.as_str()).collect(); - assert_eq!( - roots, - vec!["apps/web", "services/api"], - "the ignored root is not processed" - ); + assert_eq!(roots, vec!["apps/web", "services/api"], "the ignored root is not processed"); // Selection reports the root it excluded; nothing of it is streamed. assert!(selection .ignored_sample .iter() .any(|i| i.path == "apps/legacy/package-lock.json" && i.reason == "policy_path_excluded")); - assert!(!selection - .fetch_text - .iter() - .chain(&selection.present_only) - .any(|p| p.starts_with("apps/legacy/"))); + assert!(!selection.fetch_text.iter().chain(&selection.present_only).any(|p| p.starts_with("apps/legacy/"))); let memory_policy = memory.policy.clone().expect("policy block"); assert_eq!(memory_policy["source"], "file"); let mut disk_filtered = std::collections::BTreeSet::new(); for root in ["apps/web", "apps/legacy", "services/api"] { let disk = run_disk_in(&server, &repo, root, false); - assert_eq!( - disk.envelope["status"], "success", - "{root}: {}", - disk.stderr - ); - assert_eq!( - disk.envelope["policy"]["sha256"], memory_policy["sha256"], - "{root}" - ); + assert_eq!(disk.envelope["status"], "success", "{root}: {}", disk.stderr); + assert_eq!(disk.envelope["policy"]["sha256"], memory_policy["sha256"], "{root}"); disk_filtered.extend(filtered_set(&disk.envelope["policy"])); if let Some(project) = memory.projects.iter().find(|p| p.root == root) { assert_eq!(project.redirect, disk.envelope["redirect"], "{root}"); @@ -916,24 +883,13 @@ async fn parity_socket_yml_filters_the_same_roots_and_packages() { .collect(); assert_eq!(memory_changed, disk.changed, "{root}"); } else { - assert!( - disk.changed.is_empty(), - "{root}: an ignored root changes nothing" - ); + assert!(disk.changed.is_empty(), "{root}: an ignored root changes nothing"); } } let mut memory_filtered = filtered_set(&memory_policy); - memory_filtered.insert(( - "apps/legacy".to_string(), - None, - "policy_path_excluded".to_string(), - )); + memory_filtered.insert(("apps/legacy".to_string(), None, "policy_path_excluded".to_string())); assert_eq!(memory_filtered, disk_filtered); - assert!(disk_filtered.contains(&( - "apps/legacy".to_string(), - None, - "policy_path_excluded".to_string() - ))); + assert!(disk_filtered.contains(&("apps/legacy".to_string(), None, "policy_path_excluded".to_string()))); assert!(disk_filtered.contains(&( "services/api".to_string(), Some("pkg:cargo/serde@1.0.190".to_string()), @@ -947,17 +903,9 @@ async fn parity_socket_yml_severity_floor() { let server = MockServer::start().await; mount_api(&server, &patches).await; let memory = run_engine(&server, policy_input(&repo)).await; - let web = memory - .projects - .iter() - .find(|p| p.root == "apps/web") - .unwrap(); + let web = memory.projects.iter().find(|p| p.root == "apps/web").unwrap(); assert!(web.redirected.is_empty(), "{:#}", web.redirect); - assert!( - web.skipped.iter().any(|s| s.reason == "policy_severity"), - "{:?}", - web.skipped - ); + assert!(web.skipped.iter().any(|s| s.reason == "policy_severity"), "{:?}", web.skipped); assert!(engine_changed(&memory).is_empty()); let disk = run_disk_in(&server, &repo, "apps/web", false); assert!(disk.changed.is_empty()); @@ -983,15 +931,8 @@ async fn memory_policy_file_withheld_or_invalid_is_a_policy_error() { assert_eq!(err.code, "socket_yml_invalid"); assert!(out.projects.is_empty() && out.changed_files.is_empty() && out.policy.is_none()); // Streamed present-without-content. - let out = run_engine( - &server, - build_input(&withheld, &["socket.yml"], opts.clone()), - ) - .await; - assert_eq!( - out.policy_error.expect("policyError").code, - "socket_yml_invalid" - ); + let out = run_engine(&server, build_input(&withheld, &["socket.yml"], opts.clone())).await; + assert_eq!(out.policy_error.expect("policyError").code, "socket_yml_invalid"); // Content other than what selection read. let mut changed = repo.clone(); changed.insert("socket.yml".to_string(), b"version: 2\n".to_vec()); @@ -1002,10 +943,7 @@ async fn memory_policy_file_withheld_or_invalid_is_a_policy_error() { let mut no_sha = opts.clone(); no_sha.policy_sha256 = None; let out = run_engine(&server, build_input(&repo, &[], no_sha)).await; - assert_eq!( - out.policy_error.expect("policyError").code, - "socket_yml_invalid" - ); + assert_eq!(out.policy_error.expect("policyError").code, "socket_yml_invalid"); // Invalid content: selection refuses it before anything is fetched. let (_, bad) = policy_repo("version: 2\npatches:\n apiUrl: https://evil.example\n"); let (selection, _) = two_phase(&bad, options(false)); @@ -1020,10 +958,7 @@ async fn memory_policy_file_withheld_or_invalid_is_a_policy_error() { let mut half = opts.clone(); half.no_socket_yml = Some(true); let out = run_engine(&server, build_input(&repo, &[], half)).await; - assert_eq!( - out.policy_error.expect("policyError").code, - "socket_yml_invalid" - ); + assert_eq!(out.policy_error.expect("policyError").code, "socket_yml_invalid"); // noSocketYml skips it on both sides. let mut bypass = options(false); bypass.no_socket_yml = Some(true); @@ -1044,10 +979,7 @@ async fn memory_min_severity_option_beats_the_file() { let (_, input) = two_phase(&repo, opts); let out = run_engine(&server, input).await; let policy = out.policy.unwrap(); - assert_eq!( - policy["minSeverity"], - serde_json::json!({"value": null, "source": "flag"}) - ); + assert_eq!(policy["minSeverity"], serde_json::json!({"value": null, "source": "flag"})); assert!(out.projects.iter().any(|p| !p.redirected.is_empty())); let mut bad = options(false); bad.min_severity = Some("severe".to_string()); @@ -1056,9 +988,7 @@ async fn memory_min_severity_option_beats_the_file() { #[test] fn selection_applies_the_path_policy_and_fails_closed() { - use socket_patch_cli::hosted_memory::{ - select_paths, PolicyFileInput, SelectOptions, TreeEntryInput, - }; + use socket_patch_cli::hosted_memory::{select_paths, PolicyFileInput, SelectOptions, TreeEntryInput}; let blob = |path: &str, mode: &str| TreeEntryInput { path: path.to_string(), mode: mode.to_string(), @@ -1084,34 +1014,19 @@ fn selection_applies_the_path_policy_and_fails_closed() { }; let yml = "version: 2\npatches:\n ignorePaths: [\"/apps/old/\"]\n"; let selection = select_paths(&entries, &with(vec![text("socket.yml", yml)])); - assert!( - selection.policy_error.is_none(), - "{:?}", - selection.policy_error - ); + assert!(selection.policy_error.is_none(), "{:?}", selection.policy_error); assert_eq!(selection.policy_paths, vec!["socket.yml"]); assert_eq!(selection.policy_sha256.as_ref().map(String::len), Some(64)); assert!(selection.fetch_text.contains(&"socket.yml".to_string())); assert_eq!(selection.roots, vec!["apps/web"]); // Excluded roots (file list and built-in ignores, any case) are // reported and never streamed. - for path in [ - "apps/old/yarn.lock", - "apps/web/tests/app/package-lock.json", - "Fixtures/x/yarn.lock", - ] { + for path in ["apps/old/yarn.lock", "apps/web/tests/app/package-lock.json", "Fixtures/x/yarn.lock"] { assert!( - selection - .ignored_sample - .iter() - .any(|i| i.path == path && i.reason == "policy_path_excluded"), + selection.ignored_sample.iter().any(|i| i.path == path && i.reason == "policy_path_excluded"), "{path}: {selection:?}" ); - assert!( - !selection.fetch_text.contains(&path.to_string()) - && !selection.present_only.contains(&path.to_string()), - "{path}" - ); + assert!(!selection.fetch_text.contains(&path.to_string()) && !selection.present_only.contains(&path.to_string()), "{path}"); } // Named roots are explicit: the built-in ignores do not apply. let named = select_paths( @@ -1129,16 +1044,9 @@ fn selection_applies_the_path_policy_and_fails_closed() { text: None, missing: Some(true), }; - for files in [ - vec![], - vec![missing], - vec![text("socket.yml", "version: 2\npatches:\n apiUrl: x\n")], - ] { + for files in [vec![], vec![missing], vec![text("socket.yml", "version: 2\npatches:\n apiUrl: x\n")]] { let out = select_paths(&entries, &with(files)); - assert_eq!( - out.policy_error.as_ref().map(|e| e.code.as_str()), - Some("socket_yml_invalid") - ); + assert_eq!(out.policy_error.as_ref().map(|e| e.code.as_str()), Some("socket_yml_invalid")); assert!(out.roots.is_empty() && out.fetch_text.is_empty(), "{out:?}"); assert_eq!(out.policy_paths, vec!["socket.yml"]); } @@ -1146,14 +1054,8 @@ fn selection_applies_the_path_policy_and_fails_closed() { assert!(out.policy_error.is_some()); // A symlinked policy file is never read. entries.push(blob("socket.yaml", "120000")); - let out = select_paths( - &entries, - &with(vec![text("socket.yml", yml), text("socket.yaml", yml)]), - ); - assert_eq!( - out.policy_error.map(|e| e.code), - Some("socket_yml_invalid".to_string()) - ); + let out = select_paths(&entries, &with(vec![text("socket.yml", yml), text("socket.yaml", yml)])); + assert_eq!(out.policy_error.map(|e| e.code), Some("socket_yml_invalid".to_string())); // noSocketYml: only the built-in ignores; the file need not be passed. let out = select_paths( &entries, @@ -1184,13 +1086,8 @@ async fn memory_negation_reincludes_a_default_ignored_root() { ); let (selection, input) = two_phase(&repo, options(false)); assert_eq!(selection.roots, vec!["e2e/tests"]); - assert!(selection - .fetch_text - .contains(&"e2e/tests/package-lock.json".to_string())); - assert!( - !selection.fetch_text.iter().any(|p| p.starts_with("x/")), - "{selection:?}" - ); + assert!(selection.fetch_text.contains(&"e2e/tests/package-lock.json".to_string())); + assert!(!selection.fetch_text.iter().any(|p| p.starts_with("x/")), "{selection:?}"); assert!(selection .ignored_sample .iter() @@ -1198,31 +1095,19 @@ async fn memory_negation_reincludes_a_default_ignored_root() { let memory = run_engine(&server, input).await; let roots: Vec<&str> = memory.projects.iter().map(|p| p.root.as_str()).collect(); assert_eq!(roots, vec!["e2e/tests"]); - assert!( - !memory.projects[0].redirected.is_empty(), - "{:#}", - memory.projects[0].redirect - ); + assert!(!memory.projects[0].redirected.is_empty(), "{:#}", memory.projects[0].redirect); // Given every root anyway, the session applies the same filter itself. let direct = run_engine(&server, build_input(&repo, &[], policy_options(&repo))).await; let roots: Vec<&str> = direct.projects.iter().map(|p| p.root.as_str()).collect(); assert_eq!(roots, vec!["e2e/tests"]); let entry = &direct.policy.as_ref().unwrap()["filtered"][0]; - assert_eq!( - (entry["project"].as_str(), entry["detail"].as_str()), - (Some("x/tests"), Some("tests/ (built-in default)")) - ); + assert_eq!((entry["project"].as_str(), entry["detail"].as_str()), (Some("x/tests"), Some("tests/ (built-in default)"))); // Disk patches the same root the same way. let disk = run_disk_in(&server, &repo, "e2e/tests", false); assert_eq!(disk.envelope["status"], "success", "{}", disk.stderr); assert_eq!(memory.projects[0].redirect, disk.envelope["redirect"]); let memory_changed = engine_changed(&memory); - assert_eq!( - memory_changed, - disk.changed, - "{}", - describe(&memory_changed) - ); + assert_eq!(memory_changed, disk.changed, "{}", describe(&memory_changed)); } diff --git a/crates/socket-patch-cli/tests/hosted_memory_rollout.rs b/crates/socket-patch-cli/tests/hosted_memory_rollout.rs index 3c104abf4..ccaf92cc4 100644 --- a/crates/socket-patch-cli/tests/hosted_memory_rollout.rs +++ b/crates/socket-patch-cli/tests/hosted_memory_rollout.rs @@ -237,9 +237,7 @@ async fn memory_selected( files: &BTreeMap>, mut o: HostedScanOptions, ) -> HostedScanOutput { - use socket_patch_cli::hosted_memory::{ - select_paths, PolicyFileInput, SelectOptions, TreeEntryInput, - }; + use socket_patch_cli::hosted_memory::{select_paths, PolicyFileInput, SelectOptions, TreeEntryInput}; let entries: Vec = files .iter() .map(|(p, bytes)| TreeEntryInput { @@ -267,11 +265,7 @@ async fn memory_selected( ..SelectOptions::default() }, ); - assert!( - selection.policy_error.is_none(), - "{:?}", - selection.policy_error - ); + assert!(selection.policy_error.is_none(), "{:?}", selection.policy_error); let fetched: BTreeMap> = selection .fetch_text .iter() @@ -430,11 +424,7 @@ async fn socket_yml_policy_and_cap_converge_on_disk_and_in_memory() { b"version: 2\npatches:\n includePaths: [\"/apps/\"]\n minSeverity: high\n maxNewPatches: 2\n" .to_vec(), ); - lock( - &mut files, - "apps/one", - &["mem-a", "mem-b", "mem-c", "mem-d", "mem-e"], - ); + lock(&mut files, "apps/one", &["mem-a", "mem-b", "mem-c", "mem-d", "mem-e"]); lock(&mut files, "apps/two", &["mem-b", "mem-c", "mem-d"]); lock(&mut files, "legacy", &["mem-b", "mem-e"]); let dirs = ["apps/one", "apps/two", "legacy"]; @@ -445,16 +435,8 @@ async fn socket_yml_policy_and_cap_converge_on_disk_and_in_memory() { }; let expected: [Vec>; 3] = [ vec![vec!["mem-e", "mem-b"], vec!["mem-b"], vec![]], - vec![ - vec!["mem-e", "mem-b", "mem-c"], - vec!["mem-b", "mem-c"], - vec![], - ], - vec![ - vec!["mem-e", "mem-b", "mem-c"], - vec!["mem-b", "mem-c"], - vec![], - ], + vec![vec!["mem-e", "mem-b", "mem-c"], vec!["mem-b", "mem-c"], vec![]], + vec![vec!["mem-e", "mem-b", "mem-c"], vec!["mem-b", "mem-c"], vec![]], ]; let mut mem_files = files.clone(); @@ -467,10 +449,7 @@ async fn socket_yml_policy_and_cap_converge_on_disk_and_in_memory() { "run {}", run + 1 ); - assert_eq!( - mem.policy.as_ref().map(|p| p["source"].clone()), - Some(json!("file")) - ); + assert_eq!(mem.policy.as_ref().map(|p| p["source"].clone()), Some(json!("file"))); mem_files = apply(&mem_files, &mem); assert_eq!(&pins(&mem_files), want, "memory run {}", run + 1); @@ -490,14 +469,7 @@ async fn socket_yml_policy_and_cap_converge_on_disk_and_in_memory() { let (code, stdout, changed) = run_disk_args( &server, &disk_files, - &[ - "--no-socket-yml", - "--max-new-patches", - "1", - "apps/one", - "apps/two", - "legacy", - ], + &["--no-socket-yml", "--max-new-patches", "1", "apps/one", "apps/two", "legacy"], ); assert_eq!(code, 0, "{stdout}"); disk_files.extend(changed); diff --git a/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs b/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs index 6ce32e653..db2aab79f 100644 --- a/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs +++ b/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs @@ -519,11 +519,7 @@ fn maven_hosted_get_state_attests_without_manifest( &[(purl, vlt_hosted_common::legacy_record_from_view(&view))], ); let out = run_vex(&binary(), project, &offline); - assert_eq!( - out.code, - Some(0), - "a pre-v5 ledger record serves offline: {out}" - ); + assert_eq!(out.code, Some(0), "a pre-v5 ledger record serves offline: {out}"); assert_attested(out.doc(), purl, uuid, Marker::Redirected, &vulns); quiet.assert_no_requests(); @@ -804,11 +800,7 @@ fn nuget_hosted_manifestless_vex(root: &Path, uuid: &str, purl: &str) { &[(purl, vlt_hosted_common::legacy_record_from_view(&view))], ); let out = run(VexRun::offline()); - assert_eq!( - out.code, - Some(0), - "a pre-v5 ledger record serves offline: {out}" - ); + assert_eq!(out.code, Some(0), "a pre-v5 ledger record serves offline: {out}"); assert_attested(out.doc(), purl, uuid, Marker::Redirected, vulns); std::fs::write( diff --git a/crates/socket-patch-cli/tests/in_process_redirect.rs b/crates/socket-patch-cli/tests/in_process_redirect.rs index a3e2a6c77..066983c7a 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect.rs @@ -851,10 +851,9 @@ async fn scan_redirect_rewrites_crlf_and_bom_yarn_berry_locks_and_rollback_resto "{label}: rollback restores the pristine CRLF lock (upstream checksum \ re-derived from the registry tarball)" ); - let pkg: serde_json::Value = serde_json::from_str( - &std::fs::read_to_string(tmp.path().join("package.json")).unwrap(), - ) - .unwrap(); + let pkg: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(tmp.path().join("package.json")).unwrap()) + .unwrap(); assert!( pkg.get("resolutions").is_none(), "{label}: rollback drops the resolutions pin: {pkg}" diff --git a/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs b/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs index a78d10282..61ec4bd3f 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs @@ -308,15 +308,11 @@ async fn scan_redirect_vlt_artifact_fetch_error() { let detail = warning_detail(&doc, UNVERIFIABLE); let redacted = url.replace(&format!("/{TOKEN}/"), "//"); assert!( - detail.starts_with(&format!( - "vlt would fail to verify {redacted}: fetch error " - )) && detail.ends_with(&format!("; nothing was written for {PURL}")), + detail.starts_with(&format!("vlt would fail to verify {redacted}: fetch error ")) + && detail.ends_with(&format!("; nothing was written for {PURL}")), "the fetch-error refusal quotes the redacted URL" ); - assert!( - !detail.contains(TOKEN), - "the grant token never reaches the warning" - ); + assert!(!detail.contains(TOKEN), "the grant token never reaches the warning"); } async fn redirect_chain(hops: usize) -> (Value, tempfile::TempDir) { diff --git a/crates/socket-patch-cli/tests/in_process_redirect_pdm.rs b/crates/socket-patch-cli/tests/in_process_redirect_pdm.rs index f74c5c90c..c7adfe131 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect_pdm.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect_pdm.rs @@ -187,9 +187,7 @@ async fn mock_api(server: &MockServer) { .mount(server) .await; Mock::given(method("GET")) - .and(path_regex(format!( - "^/v0/orgs/{ORG}/patches/by-package/.+$" - ))) + .and(path_regex(format!("^/v0/orgs/{ORG}/patches/by-package/.+$"))) .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ "patches": [{ "uuid": UUID, "purl": RECORD_PURL, @@ -370,12 +368,9 @@ fn legacy_record(view: &serde_json::Value) -> serde_json::Value { .remove("publishedAt") .unwrap_or_else(|| serde_json::json!("2024-01-01T00:00:00Z")); obj.insert("exportedAt".to_string(), exported); - obj.entry("description") - .or_insert_with(|| serde_json::json!("x")); - obj.entry("license") - .or_insert_with(|| serde_json::json!("MIT")); - obj.entry("tier") - .or_insert_with(|| serde_json::json!("free")); + obj.entry("description").or_insert_with(|| serde_json::json!("x")); + obj.entry("license").or_insert_with(|| serde_json::json!("MIT")); + obj.entry("tier").or_insert_with(|| serde_json::json!("free")); record } @@ -446,11 +441,7 @@ async fn lock_only_pdm_project_redirects_attests_rescans_and_rolls_back() { // 2. Idempotent re-scan: no further edits, lock byte-identical. let code = run(hosted_args(tmp.path(), server.uri(), None)).await; assert_eq!(code, 0); - assert_eq!( - read(&lock_path), - redirected, - "re-scan must not touch the lock" - ); + assert_eq!(read(&lock_path), redirected, "re-scan must not touch the lock"); // 3. The committed state, manifest-less, attests (and only while wired). assert_manifestless_vex(tmp.path(), LOCK); @@ -503,19 +494,12 @@ async fn hatchling_build_backend_does_not_veto_the_pdm_lock_redirect() { .iter() .filter(|r| r.url.path().ends_with(&format!("/patches/view/{UUID}"))) .count(); - assert_eq!( - views, 1, - "the pdm redirect must be confirmed despite the hatch backend" - ); + assert_eq!(views, 1, "the pdm redirect must be confirmed despite the hatch backend"); assert_manifestless_vex(tmp.path(), LOCK); let code = rollback_hosted(tmp.path(), &server).await; assert_eq!(code, 0, "rollback must succeed"); - assert_eq!( - read(&lock_path), - LOCK, - "rollback must restore the pristine lock" - ); + assert_eq!(read(&lock_path), LOCK, "rollback must restore the pristine lock"); } /// The legacy `[metadata.files]` lock (lock_version 2) redirects the package diff --git a/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs b/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs index e8f743ccb..3c33af6d0 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs @@ -60,8 +60,7 @@ const MAJOR_ENV: &str = socket_patch_core::utils::pipenv::MAJOR_OVERRIDE_ENV; const LOCK: &str = include_str!("../../socket-patch-core/tests/fixtures/pipenv/2026.8.0/Pipfile.lock"); -const PIPFILE: &str = - include_str!("../../socket-patch-core/tests/fixtures/pipenv/2026.8.0/Pipfile"); +const PIPFILE: &str = include_str!("../../socket-patch-core/tests/fixtures/pipenv/2026.8.0/Pipfile"); /// The upstream and patched bytes of the record's one file, so the venv /// tests can materialize a real `Ready` (upstream) install. @@ -126,9 +125,7 @@ async fn mock_api(server: &MockServer) { .mount(server) .await; Mock::given(method("GET")) - .and(path_regex(format!( - "^/v0/orgs/{ORG}/patches/by-package/.+$" - ))) + .and(path_regex(format!("^/v0/orgs/{ORG}/patches/by-package/.+$"))) .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ "patches": [{ "uuid": UUID, "purl": RECORD_PURL, @@ -377,15 +374,8 @@ async fn lock_only_pipenv_project_redirects_attests_rescans_and_rolls_back() { ); let before: serde_json::Value = serde_json::from_str(LOCK).unwrap(); let after: serde_json::Value = serde_json::from_str(&redirected).unwrap(); - assert_eq!( - after["_meta"], before["_meta"], - "the Pipfile content hash stays" - ); - assert_eq!( - read(&tmp.path().join("Pipfile")), - PIPFILE, - "Pipfile untouched" - ); + assert_eq!(after["_meta"], before["_meta"], "the Pipfile content hash stays"); + assert_eq!(read(&tmp.path().join("Pipfile")), PIPFILE, "Pipfile untouched"); assert_no_ledger(tmp.path()); // Attested from this run's fetched record (keyed by RECORD_PURL, assume // applied) although the base purl the run confirmed differs from the @@ -393,25 +383,13 @@ async fn lock_only_pipenv_project_redirects_attests_rescans_and_rolls_back() { let vex: serde_json::Value = serde_json::from_str(&read(&vex_path)).unwrap(); let statements = vex["statements"].as_array().expect("statements"); assert_eq!(statements.len(), 1, "{vex}"); - assert_eq!( - statements[0]["vulnerability"]["name"].as_str(), - Some(GHSA), - "{vex}" - ); - assert_eq!( - statements[0]["status"].as_str(), - Some("not_affected"), - "{vex}" - ); + assert_eq!(statements[0]["vulnerability"]["name"].as_str(), Some(GHSA), "{vex}"); + assert_eq!(statements[0]["status"].as_str(), Some("not_affected"), "{vex}"); // 2. Idempotent re-scan: no further edits, lock byte-identical. let code = run(hosted_args(tmp.path(), server.uri(), None)).await; assert_eq!(code, 0); - assert_eq!( - read(&lock_path), - redirected, - "re-scan must not touch the lock" - ); + assert_eq!(read(&lock_path), redirected, "re-scan must not touch the lock"); assert_no_ledger(tmp.path()); // Manifest-less VEX over the committed state (the depscan / CI shape). @@ -421,11 +399,7 @@ async fn lock_only_pipenv_project_redirects_attests_rescans_and_rolls_back() { // 3. rollback restores the upstream registry entry. roll_back(tmp.path(), &server).await; - assert_eq!( - read(&lock_path), - LOCK, - "rollback must restore the pristine lock byte for byte" - ); + assert_eq!(read(&lock_path), LOCK, "rollback must restore the pristine lock byte for byte"); } #[tokio::test] @@ -448,10 +422,7 @@ async fn legacy_installer_major_selects_path_references() { "Pipenv 7–11 install `path` references: {redirected}" ); assert!(entry.get("file").is_none(), "{entry}"); - assert_eq!( - entry["hashes"], - serde_json::json!([format!("sha256:{}", sha256())]) - ); + assert_eq!(entry["hashes"], serde_json::json!([format!("sha256:{}", sha256())])); // The legacy `path` reference is discovered just like `file`. manifestless_vex(tmp.path(), "pipenv legacy path", &|p: &Path| { @@ -472,9 +443,7 @@ async fn stale_pipfile_lock_does_not_veto_the_requirements_redirect() { write_project(tmp.path()); // The Pipfile.lock left behind pins a DIFFERENT package; the project // installs from requirements.txt. - let stale = LOCK - .replace("\"urllib3\"", "\"six\"") - .replace("==1.26.18", "==1.16.0"); + let stale = LOCK.replace("\"urllib3\"", "\"six\"").replace("==1.26.18", "==1.16.0"); std::fs::write(tmp.path().join("Pipfile.lock"), &stale).unwrap(); // An unpatched, unhashed sibling makes the file's hash mode derivable, // so rollback can restore the hosted line (a file whose every line is a @@ -502,7 +471,10 @@ async fn stale_pipfile_lock_does_not_veto_the_requirements_redirect() { }); roll_back(tmp.path(), &server).await; - assert_eq!(read(&tmp.path().join("requirements.txt")), REQS); + assert_eq!( + read(&tmp.path().join("requirements.txt")), + REQS + ); assert_eq!(read(&tmp.path().join("Pipfile.lock")), stale); } @@ -587,27 +559,16 @@ async fn warm_venv_with_the_upstream_release_is_not_attested() { // attested and the embedded-VEX contract fails the command. let code = run(hosted_args(tmp.path(), server.uri(), Some(&vex_path))).await; let redirected = read(&lock_path); - assert!( - redirected.contains(HOSTED_URL), - "the lock is still repointed: {redirected}" - ); + assert!(redirected.contains(HOSTED_URL), "the lock is still repointed: {redirected}"); let attested = vex_path .exists() .then(|| serde_json::from_str::(&read(&vex_path)).unwrap()) .and_then(|v| v["statements"].as_array().map(Vec::len)) .unwrap_or(0); - assert_eq!( - attested, 0, - "a stale install must not be attested from the fetched record" - ); + assert_eq!(attested, 0, "a stale install must not be attested from the fetched record"); assert_ne!(code, 0, "nothing to attest fails the embedded-VEX run"); assert_eq!( - std::fs::read( - site_packages(tmp.path()) - .join("urllib3") - .join("response.py") - ) - .unwrap(), + std::fs::read(site_packages(tmp.path()).join("urllib3").join("response.py")).unwrap(), UPSTREAM, "the probe is read-only" ); diff --git a/crates/socket-patch-cli/tests/in_process_redirect_pnpm.rs b/crates/socket-patch-cli/tests/in_process_redirect_pnpm.rs index a0393d106..4519d1d4d 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect_pnpm.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect_pnpm.rs @@ -56,10 +56,7 @@ async fn rollback_hosted(cwd: &Path, server: &MockServer) -> i32 { }))) .mount(server) .await; - std::env::set_var( - "SOCKET_NPM_REGISTRY", - format!("{}/npm-registry", server.uri()), - ); + std::env::set_var("SOCKET_NPM_REGISTRY", format!("{}/npm-registry", server.uri())); let code = rollback::run(RollbackArgs { targets: Vec::new(), common: socket_patch_cli::args::GlobalArgs { @@ -808,11 +805,7 @@ async fn hosted_pnpm_manifestless_vex_from_lockfile_legacy_ledger_and_api() { ..VexRun::offline() }, ); - assert_eq!( - out.code, - Some(0), - "[{lock_name}] legacy ledger, offline: {out}" - ); + assert_eq!(out.code, Some(0), "[{lock_name}] legacy ledger, offline: {out}"); assert_attested(out.doc(), PURL, UUID, Marker::Redirected, vulns); assert_eq!(api.request_count(), seen); diff --git a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs index 605731fc5..d1b5b1607 100644 --- a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs +++ b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs @@ -1144,9 +1144,8 @@ async fn a_git_pattern_hosted_pin_is_refused_not_restored_to_the_registry() { assert!( envelope["hosted"]["failed"][0]["error"] .as_str() - .is_some_and( - |e| e.contains("installs from git") && e.contains("`git checkout -- yarn.lock`") - ), + .is_some_and(|e| e.contains("installs from git") + && e.contains("`git checkout -- yarn.lock`")), "{envelope}" ); assert_eq!( diff --git a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs b/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs index 9c08880b2..8779d2e84 100644 --- a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs +++ b/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e/vlt.rs @@ -221,10 +221,7 @@ async fn vlt_repair_reports_a_missing_ledger() { lock_bytes, "{lock:?}" ); - assert!( - tmp.path().join(rel()).join("index.js").is_file(), - "{lock:?}" - ); + assert!(tmp.path().join(rel()).join("index.js").is_file(), "{lock:?}"); } } diff --git a/crates/socket-patch-cli/tests/rollback/rollback_duality_invariants.rs b/crates/socket-patch-cli/tests/rollback/rollback_duality_invariants.rs index 31f457502..d4830cbd9 100644 --- a/crates/socket-patch-cli/tests/rollback/rollback_duality_invariants.rs +++ b/crates/socket-patch-cli/tests/rollback/rollback_duality_invariants.rs @@ -533,7 +533,8 @@ fn bare_word_target_stays_identifier_error() { )], false, ); - let manifest_before = std::fs::read(socket.join("manifest.json")).expect("read manifest bytes"); + let manifest_before = + std::fs::read(socket.join("manifest.json")).expect("read manifest bytes"); let (code, stdout, stderr) = run(tmp.path(), &["--offline", "lodash"]); assert_eq!( diff --git a/crates/socket-patch-cli/tests/scan/covgap_ecosystem_dispatch.rs b/crates/socket-patch-cli/tests/scan/covgap_ecosystem_dispatch.rs index 87d4900a3..5fee90f88 100644 --- a/crates/socket-patch-cli/tests/scan/covgap_ecosystem_dispatch.rs +++ b/crates/socket-patch-cli/tests/scan/covgap_ecosystem_dispatch.rs @@ -253,10 +253,7 @@ fn rollback_dispatch_branch_deno() { .unwrap_or_else(|e| panic!("rollback envelope must parse ({e}); stdout={stdout}")); let code = out.status.code().unwrap_or(-1); - assert_eq!( - code, 0, - "rollback --ecosystems=deno: expected exit 0; env={env}" - ); + assert_eq!(code, 0, "rollback --ecosystems=deno: expected exit 0; env={env}"); assert_eq!( env["status"], "success", "rollback --ecosystems=deno: expected success; env={env}" @@ -297,8 +294,7 @@ fn rollback_dispatch_branch_deno() { // The decisive check: the on-disk bytes are restored to ORIGINAL. let restored = std::fs::read(&verify_file).unwrap(); assert_eq!( - restored, - ORIGINAL, + restored, ORIGINAL, "rollback --ecosystems=deno: {} was not restored to its original bytes", verify_file.display() ); diff --git a/crates/socket-patch-cli/tests/scan/scan_invariants.rs b/crates/socket-patch-cli/tests/scan/scan_invariants.rs index f4bb749e1..c3316ee78 100644 --- a/crates/socket-patch-cli/tests/scan/scan_invariants.rs +++ b/crates/socket-patch-cli/tests/scan/scan_invariants.rs @@ -1787,11 +1787,7 @@ async fn report_only_scan_json_redirect_state_keys_on_lock_pins() { serde_json::json!([{ "purl": purl, "uuid": AGENT_WARN_UUID }]), "the lock pin is the record; envelope={v}" ); - assert_eq!( - state["wiringLive"], - serde_json::json!([purl]), - "envelope={v}" - ); + assert_eq!(state["wiringLive"], serde_json::json!([purl]), "envelope={v}"); // No pin, no ledger: the key must stay absent (additive contract). let clean = tempfile::tempdir().expect("tempdir"); @@ -1836,8 +1832,7 @@ async fn report_only_scan_json_ignores_a_stale_pre_v5_ledger_record() { integrity sha512-orig==\n", ) .unwrap(); - let ledger_before = - std::fs::read(tmp.path().join(".socket/vendor/redirect-state.json")).unwrap(); + let ledger_before = std::fs::read(tmp.path().join(".socket/vendor/redirect-state.json")).unwrap(); for extra in [&["--prune"][..], &["--mode", "agent", "--dry-run"][..]] { let (code, stdout, stderr) = run_scan(tmp.path(), &mock.uri(), extra); @@ -2058,7 +2053,10 @@ async fn scan_ignores_a_malformed_pre_v5_ledger() { "{extra:?}: a pre-v5 ledger is never read, so never reported: {stderr}" ); let v: serde_json::Value = serde_json::from_str(stdout.trim()).expect("valid JSON"); - assert!(v.get("redirectState").is_none(), "{extra:?}: envelope={v}"); + assert!( + v.get("redirectState").is_none(), + "{extra:?}: envelope={v}" + ); assert_eq!( std::fs::read(vendor_dir.join("redirect-state.json")).unwrap(), b"{ torn ledger", @@ -2092,11 +2090,7 @@ async fn ecosystems_filter_keeps_records_but_not_wiring_live() { /*with_record=*/ true, ); - let (code, stdout, stderr) = run_scan( - tmp.path(), - &mock.uri(), - &["--mode", "agent", "--dry-run", "--ecosystems", "pypi"], - ); + let (code, stdout, stderr) = run_scan(tmp.path(), &mock.uri(), &["--mode", "agent", "--dry-run", "--ecosystems", "pypi"]); assert_eq!(code, 0, "stdout={stdout}; stderr={stderr}"); let v: serde_json::Value = serde_json::from_str(stdout.trim()).expect("valid JSON"); let state = &v["redirectState"]; diff --git a/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs index 64ea1197c..8ad94e551 100644 --- a/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs +++ b/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs @@ -216,11 +216,7 @@ async fn paths_scope_narrows_the_query() { let tmp = tempfile::tempdir().unwrap(); write_two_subtree_project(tmp.path()); - let (code, stdout, stderr) = run_scan( - tmp.path(), - &server.uri(), - &["packages/app", "--mode", "agent", "--dry-run"], - ); + let (code, stdout, stderr) = run_scan(tmp.path(), &server.uri(), &["packages/app", "--mode", "agent", "--dry-run"]); assert_eq!( code, 0, "scoped scan must exit 0; stdout={stdout}; stderr={stderr}" @@ -481,11 +477,7 @@ async fn supplements_excluded_with_warning() { // purl reaches the API. let scoped_server = MockServer::start().await; mock_batch_empty(&scoped_server).await; - let (code, stdout, stderr) = run_scan( - tmp.path(), - &scoped_server.uri(), - &["packages/app", "--mode", "agent", "--dry-run"], - ); + let (code, stdout, stderr) = run_scan(tmp.path(), &scoped_server.uri(), &["packages/app", "--mode", "agent", "--dry-run"]); assert_eq!( code, 0, "scoped scan must exit 0; stdout={stdout}; stderr={stderr}" diff --git a/crates/socket-patch-cli/tests/update/covgap_commands_update.rs b/crates/socket-patch-cli/tests/update/covgap_commands_update.rs index 16836e614..b2d75aafc 100644 --- a/crates/socket-patch-cli/tests/update/covgap_commands_update.rs +++ b/crates/socket-patch-cli/tests/update/covgap_commands_update.rs @@ -268,8 +268,9 @@ mod pty { let mut child = pair.slave.spawn_command(cmd).expect("spawn in PTY"); drop(pair.slave); - let reader_handle = - crate::pty_io::PtyOutput::spawn(pair.master.try_clone_reader().expect("clone reader")); + let reader_handle = crate::pty_io::PtyOutput::spawn( + pair.master.try_clone_reader().expect("clone reader"), + ); let mut killer = child.clone_killer(); std::thread::spawn(move || { @@ -337,8 +338,7 @@ mod pty { "a declined update exits 1 (codebase convention); got: {output}" ); assert!( - !output.contains("Updated socket-patch") - && !output.contains("Reinstalled socket-patch"), + !output.contains("Updated socket-patch") && !output.contains("Reinstalled socket-patch"), "a declined update must not report a swap; got: {output}" ); diff --git a/crates/socket-patch-cli/tests/yarn_berry_common/mod.rs b/crates/socket-patch-cli/tests/yarn_berry_common/mod.rs index dffab3915..e8ff74216 100644 --- a/crates/socket-patch-cli/tests/yarn_berry_common/mod.rs +++ b/crates/socket-patch-cli/tests/yarn_berry_common/mod.rs @@ -660,9 +660,7 @@ pub fn run_manifestless_vex_matrix(flow: &BerryVexFlow<'_>) -> Vec { crate::vex_e2e_common::assert_no_hosted_ledger(&fresh, "manifest-deleted"); } else { assert!( - fresh - .join(socket_patch_core::vendor::VENDOR_STATE_REL) - .is_file(), + fresh.join(socket_patch_core::vendor::VENDOR_STATE_REL).is_file(), "manifest-deleted: the vendored flow must have left its .socket/vendor ledger" ); } diff --git a/crates/socket-patch-core/src/api/ranking.rs b/crates/socket-patch-core/src/api/ranking.rs index 58ca27078..949931782 100644 --- a/crates/socket-patch-core/src/api/ranking.rs +++ b/crates/socket-patch-core/src/api/ranking.rs @@ -164,14 +164,8 @@ pub fn batch_supersedes(candidate: &BatchPatchInfo, applied: &BatchPatchInfo) -> /// classify a recorded patch (ALREADY vs UPGRADE) and to report /// `updates[]`, on the same records that pick the patch, so selection, /// classification and reporting cannot disagree. -pub fn search_result_supersedes( - candidate: &PatchSearchResult, - recorded: &PatchSearchResult, -) -> bool { - key_supersedes( - &rank_search_result(candidate), - &rank_search_result(recorded), - ) +pub fn search_result_supersedes(candidate: &PatchSearchResult, recorded: &PatchSearchResult) -> bool { + key_supersedes(&rank_search_result(candidate), &rank_search_result(recorded)) } fn key_supersedes(c: &RankKey<'_>, a: &RankKey<'_>) -> bool { @@ -377,7 +371,12 @@ mod tests { "2020-01-01T00:00:00Z", &["critical", "high"] ), - search_multi("z_new_low", "free", "2026-08-01T00:00:00Z", &["low", "low"]), + search_multi( + "z_new_low", + "free", + "2026-08-01T00:00:00Z", + &["low", "low"] + ), ]), "a_old_critical" ); diff --git a/crates/socket-patch-core/src/crawlers/python_crawler.rs b/crates/socket-patch-core/src/crawlers/python_crawler.rs index ad4125e15..7019e8945 100644 --- a/crates/socket-patch-core/src/crawlers/python_crawler.rs +++ b/crates/socket-patch-core/src/crawlers/python_crawler.rs @@ -582,7 +582,9 @@ async fn pdm_saved_interpreter(cwd: &Path) -> Option { let saved = match read_regular_to_string(&cwd.join(".pdm-python")).await { Ok(text) => text.trim().to_string(), Err(_) => { - let text = read_regular_to_string(&cwd.join(".pdm.toml")).await.ok()?; + let text = read_regular_to_string(&cwd.join(".pdm.toml")) + .await + .ok()?; let doc = text.parse::().ok()?; doc.get("python")?.get("path")?.as_str()?.trim().to_string() } @@ -3668,11 +3670,7 @@ mod tests { fake_venv(&tmp.path().join("uv-env"), "venv"); let uv_env = env_of(&[( "UV_PROJECT_ENVIRONMENT", - tmp.path() - .join("uv-env") - .join("venv") - .to_string_lossy() - .into_owned(), + tmp.path().join("uv-env").join("venv").to_string_lossy().into_owned(), )]); assert_eq!( find_local_venv_site_packages_with(&project, &uv_env).await, diff --git a/crates/socket-patch-core/src/formats/cargo/mod.rs b/crates/socket-patch-core/src/formats/cargo/mod.rs index 3e9cb9c5b..58b4dc2bc 100644 --- a/crates/socket-patch-core/src/formats/cargo/mod.rs +++ b/crates/socket-patch-core/src/formats/cargo/mod.rs @@ -34,6 +34,7 @@ use crate::utils::purl::simple_purl; use crate::vendor::cargo_tag; use crate::vendor::lock_inventory::{LockIntegrity, LockfileEntry, SourceKind}; + // ── entry model ── /// The `[metadata]` key a v1 lock files `name`+`version`'s checksum under. @@ -331,6 +332,7 @@ pub(crate) fn parse_ref(spelled: &str) -> (&str, Option<&str>, Option<&str>) { (name, version, source) } + // ── the model ── /// One `Cargo.lock`, parsed once (see the module docs). @@ -498,13 +500,7 @@ impl CargoLock { uuid: &str, copy_tagged: bool, ) -> CopyClaim<'_> { - vendored_copy_claim( - &self.packages, - &self.unused, - name, - version, - uuid, - copy_tagged, - ) + vendored_copy_claim(&self.packages, &self.unused, name, version, uuid, copy_tagged) } } + diff --git a/crates/socket-patch-core/src/formats/composer/mod.rs b/crates/socket-patch-core/src/formats/composer/mod.rs index d45156ceb..8efa3c178 100644 --- a/crates/socket-patch-core/src/formats/composer/mod.rs +++ b/crates/socket-patch-core/src/formats/composer/mod.rs @@ -22,6 +22,7 @@ use crate::utils::digest::sha1_hex; use crate::vendor::lock_inventory::{http_url, LockIntegrity, LockfileEntry, SourceKind}; use crate::vendor::path::{parse_vendor_path, VendorPathParts}; + // ── entry model ── /// One entry of a parsed `composer.lock` (see [`composer_lock_packages`]). @@ -106,6 +107,7 @@ pub(crate) fn composer_lock_packages(doc: &Value) -> Vec out } + // ── the model ── /// One `composer.lock`, read once (see the module docs). @@ -175,3 +177,4 @@ impl<'a> ComposerLock<'a> { out } } + diff --git a/crates/socket-patch-core/src/formats/gem/gemfile.rs b/crates/socket-patch-core/src/formats/gem/gemfile.rs index 34232745f..8203c8a0a 100644 --- a/crates/socket-patch-core/src/formats/gem/gemfile.rs +++ b/crates/socket-patch-core/src/formats/gem/gemfile.rs @@ -370,14 +370,8 @@ mod tests { #[test] fn escaped_quotes_and_hashes_inside_strings_stay_in_the_string() { - assert_eq!( - key(", require: 'it\\'s', gitlab: \"x\""), - Some("gitlab:".into()) - ); - assert_eq!( - key(", require: \"a\\\"b\", git: \"x\""), - Some("git:".into()) - ); + assert_eq!(key(", require: 'it\\'s', gitlab: \"x\""), Some("gitlab:".into())); + assert_eq!(key(", require: \"a\\\"b\", git: \"x\""), Some("git:".into())); assert_eq!(key(", local: \"#{name}\""), Some("local:".into())); } diff --git a/crates/socket-patch-core/src/formats/gem/hosted.rs b/crates/socket-patch-core/src/formats/gem/hosted.rs index 5dd4dc8b3..0416c3594 100644 --- a/crates/socket-patch-core/src/formats/gem/hosted.rs +++ b/crates/socket-patch-core/src/formats/gem/hosted.rs @@ -304,3 +304,4 @@ pub(crate) fn checksum_entry_span(lock: &str, name: &str, version: &str) -> Opti } None } + diff --git a/crates/socket-patch-core/src/formats/gem/mod.rs b/crates/socket-patch-core/src/formats/gem/mod.rs index 21bd39008..3a8f17af2 100644 --- a/crates/socket-patch-core/src/formats/gem/mod.rs +++ b/crates/socket-patch-core/src/formats/gem/mod.rs @@ -28,6 +28,7 @@ use crate::utils::digest::sha256_hex; use crate::utils::purl::simple_purl; use crate::vendor::lock_inventory::{http_url, LockIntegrity, LockfileEntry, SourceKind}; + /// The Bundler lockfiles, legacy spelling first: `Gemfile.lock` and /// `gems.locked` (what bundler writes instead when the manifest is /// `gems.rb`). @@ -222,6 +223,7 @@ impl<'t> GemfileLock<'t> { } } + /// Where a rubygems-compatible registry at `base` (no trailing `/`) serves /// `name`-`version`'s `.gem` — the inventory's resolved URL and ledger /// recovery's fetch URL. `None` for a non-http(s) base. diff --git a/crates/socket-patch-core/src/formats/mod.rs b/crates/socket-patch-core/src/formats/mod.rs index 31ed9059e..f3ea013a3 100644 --- a/crates/socket-patch-core/src/formats/mod.rs +++ b/crates/socket-patch-core/src/formats/mod.rs @@ -26,13 +26,13 @@ //! [`registry()`] is the one table of which project files carry a lock or //! its wiring, and in which roles. -pub(crate) mod bun; pub mod cargo; pub mod composer; pub mod gem; pub(crate) mod maven; pub(crate) mod nuget; pub mod pnpm; +pub(crate) mod bun; pub mod registry; pub mod yarn; @@ -81,11 +81,7 @@ mod architecture_tests { .filter(|l| !l.trim_start().starts_with("//")) .collect::>() .join("\n"); - let used: Vec<&str> = IMPURE - .iter() - .copied() - .filter(|n| code.contains(n)) - .collect(); + let used: Vec<&str> = IMPURE.iter().copied().filter(|n| code.contains(n)).collect(); assert!( used.is_empty(), "{}: a format model uses {used:?} — models are pure (module docs)", diff --git a/crates/socket-patch-core/src/formats/pnpm/mod.rs b/crates/socket-patch-core/src/formats/pnpm/mod.rs index a632c9c3a..c7d47c1f2 100644 --- a/crates/socket-patch-core/src/formats/pnpm/mod.rs +++ b/crates/socket-patch-core/src/formats/pnpm/mod.rs @@ -39,6 +39,7 @@ use crate::utils::digest::is_sri_pin; use crate::vendor::lock_inventory::{http_url, LockIntegrity, LockfileEntry}; use crate::vendor::path::parse_vendor_path; + // ── entry model ── /// One `packages:` entry of a pnpm lock, read with the entry grammar @@ -282,10 +283,7 @@ fn lock_versions(text: &str) -> impl Iterator, u32)> + '_ { let value = rest.trim().trim_matches(|c| c == '\'' || c == '"'); let mut parts = value.split('.'); let major = parts.next().and_then(|m| m.parse::().ok()); - let minor = parts - .next() - .and_then(|m| m.parse::().ok()) - .unwrap_or(0); + let minor = parts.next().and_then(|m| m.parse::().ok()).unwrap_or(0); Some((major, minor)) }) } @@ -305,8 +303,7 @@ pub fn lock_version_major(text: &str) -> Option { /// rejects it): a `shrinkwrapVersion` lock (pnpm 1–2) or lockfileVersion /// 5.0–5.2 (pnpm 3–5). Later locks never get the `--store` note. pub fn may_need_store_flag(text: &str) -> bool { - text.lines() - .any(|line| line.starts_with("shrinkwrapVersion:")) + text.lines().any(|line| line.starts_with("shrinkwrapVersion:")) || lock_versions(text).any(|(major, minor)| major == Some(5) && minor <= 2) } @@ -494,9 +491,7 @@ pub(crate) fn vendored_npm_uuids(text: &str) -> HashSet { if !in_section { continue; } - if let Some(uuid) = - lines::parse_key_line(line, 2).and_then(|(key, _, _)| vendored_npm_uuid(key)) - { + if let Some(uuid) = lines::parse_key_line(line, 2).and_then(|(key, _, _)| vendored_npm_uuid(key)) { out.insert(uuid); } } @@ -513,52 +508,17 @@ mod tests { fn resolves_reads_every_key_generation_boundary_anchored() { let lock = |keys: &str| format!("lockfileVersion: '9.0'\n\npackages:\n\n{keys}"); let yes = [ - ( - " left-pad@1.3.0:\n resolution: {integrity: sha512-x}\n", - "left-pad", - "1.3.0", - ), - ( - " /left-pad@1.3.0:\n resolution: {}\n", - "left-pad", - "1.3.0", - ), - ( - " /left-pad/1.3.0:\n resolution: {}\n", - "left-pad", - "1.3.0", - ), - ( - " 'left-pad@1.3.0(react@18.0.0)':\n dev: false\n", - "left-pad", - "1.3.0", - ), - ( - " /left-pad/1.3.0_react@18.0.0:\n dev: false\n", - "left-pad", - "1.3.0", - ), - ( - " '@scope/name@1.0.0':\n dev: false\n", - "@scope/name", - "1.0.0", - ), - ( - " /@scope/name@1.0.0:\n dev: false\n", - "@scope/name", - "1.0.0", - ), - ( - " /@scope/name/1.0.0:\n dev: false\n", - "@scope/name", - "1.0.0", - ), + (" left-pad@1.3.0:\n resolution: {integrity: sha512-x}\n", "left-pad", "1.3.0"), + (" /left-pad@1.3.0:\n resolution: {}\n", "left-pad", "1.3.0"), + (" /left-pad/1.3.0:\n resolution: {}\n", "left-pad", "1.3.0"), + (" 'left-pad@1.3.0(react@18.0.0)':\n dev: false\n", "left-pad", "1.3.0"), + (" /left-pad/1.3.0_react@18.0.0:\n dev: false\n", "left-pad", "1.3.0"), + (" '@scope/name@1.0.0':\n dev: false\n", "@scope/name", "1.0.0"), + (" /@scope/name@1.0.0:\n dev: false\n", "@scope/name", "1.0.0"), + (" /@scope/name/1.0.0:\n dev: false\n", "@scope/name", "1.0.0"), ]; for (keys, name, version) in yes { - assert!( - PnpmLock::parse(&lock(keys)).resolves(name, version), - "{keys}" - ); + assert!(PnpmLock::parse(&lock(keys)).resolves(name, version), "{keys}"); } let no = [ (" left-pad@1.3.0-beta.1:\n dev: false\n", "left-pad", "1.3.0"), @@ -574,10 +534,7 @@ mod tests { ), ]; for (keys, name, version) in no { - assert!( - !PnpmLock::parse(&lock(keys)).resolves(name, version), - "{keys}" - ); + assert!(!PnpmLock::parse(&lock(keys)).resolves(name, version), "{keys}"); } // Keys outside `packages:` (importers, overrides) resolve nothing. let importers = "lockfileVersion: '9.0'\n\nimporters:\n\n left-pad@1.3.0:\n x: y\n"; @@ -600,10 +557,7 @@ mod tests { let other = "22222222-2222-4222-8222-222222222222"; assert!(!PnpmLock::parse(text).vendored_in_use(other)); let crlf = text.replace('\n', "\r\n"); - assert!( - PnpmLock::parse(&crlf).vendored_in_use(UUID), - "CRLF reads like LF" - ); + assert!(PnpmLock::parse(&crlf).vendored_in_use(UUID), "CRLF reads like LF"); } // An overrides declaration alone is not usage. let overrides = format!( diff --git a/crates/socket-patch-core/src/formats/registry.rs b/crates/socket-patch-core/src/formats/registry.rs index de6adedb1..c690f6be8 100644 --- a/crates/socket-patch-core/src/formats/registry.rs +++ b/crates/socket-patch-core/src/formats/registry.rs @@ -68,11 +68,7 @@ const fn row(path: &'static str, ecosystem: &'static str, roles: u8) -> FormatFi const REGISTRY: &[FormatFile] = &[ // ── npm family ── row("package-lock.json", "npm", HOSTED | VENDORED | PROBE | ROOT), - row( - "npm-shrinkwrap.json", - "npm", - HOSTED | VENDORED | PROBE | ROOT, - ), + row("npm-shrinkwrap.json", "npm", HOSTED | VENDORED | PROBE | ROOT), row( "pnpm-lock.yaml", "npm", @@ -123,11 +119,7 @@ const REGISTRY: &[FormatFile] = &[ row(".cargo/config", "cargo", HOSTED | VENDORED | PROBE), // ── composer ── row("composer.json", "composer", VENDORED), - row( - "composer.lock", - "composer", - HOSTED | VENDORED | PROBE | ROOT, - ), + row("composer.lock", "composer", HOSTED | VENDORED | PROBE | ROOT), // ── nuget ── row("nuget.config", "nuget", HOSTED | PROBE), row("NuGet.config", "nuget", HOSTED | PROBE), @@ -279,11 +271,7 @@ mod tests { paths.dedup(); assert_eq!(before, paths.len(), "duplicate registry path"); for f in REGISTRY.iter().filter(|f| f.has(ROOT)) { - assert!( - !f.path.contains('/'), - "{}: a root marker is a basename", - f.path - ); + assert!(!f.path.contains('/'), "{}: a root marker is a basename", f.path); } } diff --git a/crates/socket-patch-core/src/formats/yarn/mod.rs b/crates/socket-patch-core/src/formats/yarn/mod.rs index 3c7e71eec..389dadde0 100644 --- a/crates/socket-patch-core/src/formats/yarn/mod.rs +++ b/crates/socket-patch-core/src/formats/yarn/mod.rs @@ -64,10 +64,7 @@ mod tests { #[test] fn sniff_prefers_berry_and_skips_a_bom() { - assert_eq!( - sniff_grammar("__metadata:\n version: 8\n"), - Some(YarnLockGrammar::Berry) - ); + assert_eq!(sniff_grammar("__metadata:\n version: 8\n"), Some(YarnLockGrammar::Berry)); assert_eq!( sniff_grammar("\u{feff}# yarn lockfile v1\r\n"), Some(YarnLockGrammar::Classic) diff --git a/crates/socket-patch-core/src/hosted/guidance.rs b/crates/socket-patch-core/src/hosted/guidance.rs index 81bf03d76..51ec85483 100644 --- a/crates/socket-patch-core/src/hosted/guidance.rs +++ b/crates/socket-patch-core/src/hosted/guidance.rs @@ -173,7 +173,9 @@ pub fn pnpm_lock_carries_hosted_redirect( pub fn npm_lock_url_needles(artifact_url: &str) -> Vec { let mut needles: Vec = crate::patch::redirect::artifact_url_spellings(artifact_url).into(); - needles.push(crate::utils::uri::encode_uri_component(artifact_url)); + needles.push(crate::utils::uri::encode_uri_component( + artifact_url, + )); needles } @@ -308,7 +310,11 @@ fn npm_allow_remote_preamble(hosts: &[&str]) -> String { /// The auto-config variant: `allow-remote=all` was (or, on `--dry-run`, /// would be) written to the project `.npmrc`, so installs need no flags. -pub fn npm_allow_remote_configured_detail(hosts: &[&str], created: bool, dry_run: bool) -> String { +pub fn npm_allow_remote_configured_detail( + hosts: &[&str], + created: bool, + dry_run: bool, +) -> String { let how = match (created, dry_run) { (true, false) => "`allow-remote=all` was written to a new", (false, false) => "`allow-remote=all` was appended to the existing", diff --git a/crates/socket-patch-core/src/hosted/memory/discover.rs b/crates/socket-patch-core/src/hosted/memory/discover.rs index 9469b50ba..6475a0cc0 100644 --- a/crates/socket-patch-core/src/hosted/memory/discover.rs +++ b/crates/socket-patch-core/src/hosted/memory/discover.rs @@ -14,7 +14,9 @@ use std::time::Duration; use crate::api::client::{ApiError, ApiFuture, PatchApi}; use crate::api::ranking::cmp_search_results; -use crate::api::types::{BatchPackagePatches, PackageVendorResult, PatchResponse, SearchResponse}; +use crate::api::types::{ + BatchPackagePatches, PackageVendorResult, PatchResponse, SearchResponse, +}; use crate::utils::purl::{normalize_purl, strip_purl_qualifiers}; use super::types::MAX_REFERENCE_BATCH; diff --git a/crates/socket-patch-core/src/hosted/memory/limits.rs b/crates/socket-patch-core/src/hosted/memory/limits.rs index da4dd695d..9f895d6c9 100644 --- a/crates/socket-patch-core/src/hosted/memory/limits.rs +++ b/crates/socket-patch-core/src/hosted/memory/limits.rs @@ -42,7 +42,12 @@ impl ResolvedOptions { /// as `flag`), then the socket.yml `patches.maxNewPatches`, then /// unlimited; `maxNewPatchesCap` only tightens it. pub(crate) fn max_new(&self, file: Option) -> crate::rollout::MaxNew { - crate::rollout::resolve_max_new(self.max_new_patches, None, file, self.max_new_patches_cap) + crate::rollout::resolve_max_new( + self.max_new_patches, + None, + file, + self.max_new_patches_cap, + ) } } @@ -74,9 +79,8 @@ pub(crate) fn resolve_options(options: &HostedScanOptions) -> Result None, Some(value) => Some(( - crate::policy::parse_min_severity(value).map_err(|e| { - EngineError::invalid("invalid_min_severity", format!("minSeverity: {e}")) - })?, + crate::policy::parse_min_severity(value) + .map_err(|e| EngineError::invalid("invalid_min_severity", format!("minSeverity: {e}")))?, crate::policy::OverrideSource::Flag, )), }; diff --git a/crates/socket-patch-core/src/hosted/memory/mod.rs b/crates/socket-patch-core/src/hosted/memory/mod.rs index 51651e5fc..85a0d3c48 100644 --- a/crates/socket-patch-core/src/hosted/memory/mod.rs +++ b/crates/socket-patch-core/src/hosted/memory/mod.rs @@ -58,17 +58,17 @@ pub use limits::SessionBuilder; pub use select::{candidate_files, safe_repo_path, select_paths}; pub use types::*; -use crate::policy::{ - canon, patch_severity_order, policy_block, FilterReason, FilteredEntry, MemoryPolicyFs, - PolicyError, PolicySource, Root, RootFile, SelectionPolicy, PATCHES_DISABLED, - POLICY_FILE_NAMES, -}; use crate::rollout::stage::{ classify, lookup_incomplete, mentioned_uuids, offers_from_results, Offers, RecordedIndex, Row, - Stage, ROLLOUT_DEFERRED, + Stage, + ROLLOUT_DEFERRED, }; use discover::Provider; use stages::{Planned, RewriteRefused, Rewritten, StageOptions}; +use crate::policy::{ + canon, patch_severity_order, policy_block, FilterReason, FilteredEntry, MemoryPolicyFs, PolicyError, + PolicySource, Root, RootFile, SelectionPolicy, PATCHES_DISABLED, POLICY_FILE_NAMES, +}; /// `"+"`; the sha comes from the /// `SOCKET_PATCH_GIT_SHA` build-time variable. @@ -419,8 +419,11 @@ fn memory_recorded( .map(|p| (purl.clone(), p.uuid.clone())) }) .collect(); - let merged = - crate::ledgers::merge_ledger_records_for_updates(manifest.as_ref(), vendor.as_ref(), &pins); + let merged = crate::ledgers::merge_ledger_records_for_updates( + manifest.as_ref(), + vendor.as_ref(), + &pins, + ); RecordedIndex::new(merged.as_deref(), &pins) } @@ -447,20 +450,13 @@ async fn engine( // The repo's socket.yml policy, before any root is processed: a file // that cannot be honored fails the whole session closed. - let (policy, policy_warnings) = match SelectionPolicy::load( - &memory_policy_fs(&files, &options.policy_paths), - &options.policy_overrides, - ) { - Ok(loaded) => loaded, - Err(error) => { - return Ok(policy_error_output( - &error, - warnings, - files_input, - bytes_input, - )); - } - }; + let (policy, policy_warnings) = + match SelectionPolicy::load(&memory_policy_fs(&files, &options.policy_paths), &options.policy_overrides) { + Ok(loaded) => loaded, + Err(error) => { + return Ok(policy_error_output(&error, warnings, files_input, bytes_input)); + } + }; // Path selection chose which files to send by the policy it read; a // different policy here would judge roots it never fetched. let read = match policy.source() { @@ -469,27 +465,16 @@ async fn engine( }; // Selection returns no digest when it bypassed the file, so a digest // with a bypassed session means the two sides disagree. - let expected = if options.policy_overrides.bypass { - None - } else { - read.map(|(_, sha)| sha) - }; + let expected = if options.policy_overrides.bypass { None } else { read.map(|(_, sha)| sha) }; if expected != options.policy_sha256.as_deref() { let error = PolicyError::Invalid { - file: read - .map_or(POLICY_FILE_NAMES[0], |(path, _)| path) - .to_string(), + file: read.map_or(POLICY_FILE_NAMES[0], |(path, _)| path).to_string(), key: String::new(), message: "the policy content differs from the one path selection read: pass \ selectHostedScanPaths' policySha256 and stream the same text" .to_string(), }; - return Ok(policy_error_output( - &error, - warnings, - files_input, - bytes_input, - )); + return Ok(policy_error_output(&error, warnings, files_input, bytes_input)); } for w in policy_warnings { warnings.push(EngineWarning::new(w.code, w.detail, None)); @@ -737,25 +722,23 @@ async fn engine( // the tree's manifest and vendor ledger, and the hosted pins its // lockfiles name. ALREADY rows carry the recorded uuid, so a re-scan // re-confirms a pin instead of swapping it. - let mut stage = Stage::new( - options.max_new(policy.max_new_patches()), - None, - std::path::Path::new(""), - ); + let mut stage = Stage::new(options.max_new(policy.max_new_patches()), None, std::path::Path::new("")); // A root whose every lookup failed hides packages that could have been // NEW: a capped run then admits none anywhere (§5.2). - stage.incomplete |= states.iter().any(|s| { - s.error - .as_ref() - .is_some_and(|e| e.code == "patch_lookup_failed") - }); + stage.incomplete |= states + .iter() + .any(|s| s.error.as_ref().is_some_and(|e| e.code == "patch_lookup_failed")); let roots_by_path: Vec = states.iter().map(|s| s.root.clone()).collect(); for state in states.iter_mut().filter(|s| s.error.is_none()) { let Some(project) = state.project.as_ref() else { continue; }; let recorded = memory_recorded(project, &state.root, &roots_by_path, &state.offers); - stage.incomplete |= lookup_incomplete(&recorded, &state.failed_details, batch_failed); + stage.incomplete |= lookup_incomplete( + &recorded, + &state.failed_details, + batch_failed, + ); let mut rows = classify(&state.offers, &recorded, &state.root); for row in &mut rows { row.candidate.in_flight = options.in_flight.contains(&row.candidate.base_purl); @@ -890,11 +873,8 @@ async fn engine( unknown_roots.contains(&row.candidate.project) || confirmed.contains(&(row.candidate.project.clone(), row.writer.uuid.clone())) }); - let deferred_rows: Vec<(crate::rollout::Candidate, u32)> = stage - .plan - .as_ref() - .map(|p| p.deferred.clone()) - .unwrap_or_default(); + let deferred_rows: Vec<(crate::rollout::Candidate, u32)> = + stage.plan.as_ref().map(|p| p.deferred.clone()).unwrap_or_default(); if !deferred_rows.is_empty() { let root_index: BTreeMap = states .iter() @@ -1146,10 +1126,7 @@ fn select_with_policy( let mut by_purl: BTreeMap> = BTreeMap::new(); for (patch, reason) in dropped { if !chosen.contains(patch.purl.as_str()) { - by_purl - .entry(patch.purl.clone()) - .or_default() - .push((patch, reason)); + by_purl.entry(patch.purl.clone()).or_default().push((patch, reason)); } } for (purl, mut group) in by_purl { diff --git a/crates/socket-patch-core/src/hosted/memory/roots.rs b/crates/socket-patch-core/src/hosted/memory/roots.rs index 1a68e6528..35f39be1a 100644 --- a/crates/socket-patch-core/src/hosted/memory/roots.rs +++ b/crates/socket-patch-core/src/hosted/memory/roots.rs @@ -31,23 +31,17 @@ pub const UNSUPPORTED_MARKERS: [(&str, &[&str]); 2] = [ /// trees, VCS and tool state, and vendored dependencies. Structural, so no /// policy can negate them. (Test and fixture trees are the socket.yml /// policy's overridable built-in ignores.) -pub(crate) const EXCLUDED_ROOT_SEGMENTS: [&str; 5] = - ["node_modules", ".git", ".socket", ".yarn", "vendor"]; +pub(crate) const EXCLUDED_ROOT_SEGMENTS: [&str; 5] = ["node_modules", ".git", ".socket", ".yarn", "vendor"]; /// The marker basenames of `root` among `paths` (the files the policy's /// path filters test for that root). -pub(crate) fn root_markers<'a>( - root: &str, - paths: impl IntoIterator, -) -> Vec { +pub(crate) fn root_markers<'a>(root: &str, paths: impl IntoIterator) -> Vec { let mut out: Vec = paths .into_iter() .filter_map(|path| { let (dir, base) = split_path(path); let marker = marker_ecosystem(base).is_some() - || UNSUPPORTED_MARKERS - .iter() - .any(|(_, names)| names.contains(&base)); + || UNSUPPORTED_MARKERS.iter().any(|(_, names)| names.contains(&base)); (dir == root && marker).then(|| base.to_string()) }) .collect(); @@ -208,15 +202,7 @@ mod tests { #[test] fn root_markers_name_every_marker_of_the_root_only() { assert_eq!( - root_markers( - "a", - [ - "a/yarn.lock", - "a/package.json", - "a/b/yarn.lock", - "a/pom.xml" - ] - ), + root_markers("a", ["a/yarn.lock", "a/package.json", "a/b/yarn.lock", "a/pom.xml"]), vec!["pom.xml".to_string(), "yarn.lock".to_string()] ); } diff --git a/crates/socket-patch-core/src/hosted/memory/select.rs b/crates/socket-patch-core/src/hosted/memory/select.rs index 04dcee403..f18efa81e 100644 --- a/crates/socket-patch-core/src/hosted/memory/select.rs +++ b/crates/socket-patch-core/src/hosted/memory/select.rs @@ -15,8 +15,8 @@ use crate::patch::redirect::npmrc::NPMRC_REL; use crate::utils::python_lock::is_python_lock_name; use crate::policy::{ - MemoryPolicyFs, PolicyOverrides, PolicySource, Root, RootFile, SelectionPolicy, - POLICY_FILE_NAMES, SOCKET_YML_INVALID, + MemoryPolicyFs, PolicyOverrides, PolicySource, Root, RootFile, SelectionPolicy, POLICY_FILE_NAMES, + SOCKET_YML_INVALID, }; use super::roots::{ @@ -185,10 +185,7 @@ fn classify(rel: &str, root_files: &BTreeSet<&str>) -> Option { /// The listed root policy files with the text the caller fetched first. A /// listed file with no text (not passed, `missing`, or a symlink) is present /// without content, so loading it fails closed. -fn selection_policy_fs( - blobs: &BTreeMap, - supplied: &[PolicyFileInput], -) -> MemoryPolicyFs { +fn selection_policy_fs(blobs: &BTreeMap, supplied: &[PolicyFileInput]) -> MemoryPolicyFs { let mut fs = MemoryPolicyFs::default(); for name in POLICY_FILE_NAMES { let Some(&symlink) = blobs.get(name) else { @@ -214,10 +211,7 @@ fn selection_policy( options: &SelectOptions, ) -> Result { let supplied = options.policy_files.as_deref().unwrap_or_default(); - if let Some(bad) = supplied - .iter() - .find(|f| !POLICY_FILE_NAMES.contains(&f.path.as_str())) - { + if let Some(bad) = supplied.iter().find(|f| !POLICY_FILE_NAMES.contains(&f.path.as_str())) { return Err(PolicyErrorInfo { code: SOCKET_YML_INVALID.to_string(), detail: format!( diff --git a/crates/socket-patch-core/src/hosted/memory/types.rs b/crates/socket-patch-core/src/hosted/memory/types.rs index fa81876e8..2a11daed7 100644 --- a/crates/socket-patch-core/src/hosted/memory/types.rs +++ b/crates/socket-patch-core/src/hosted/memory/types.rs @@ -171,9 +171,7 @@ impl<'de> Deserialize<'de> for MaxNewPatchesOption { if v == "none" { Ok(MaxNewPatchesOption(None)) } else { - Err(E::custom(format!( - "maxNewPatches must be a number or \"none\", not `{v}`" - ))) + Err(E::custom(format!("maxNewPatches must be a number or \"none\", not `{v}`"))) } } } diff --git a/crates/socket-patch-core/src/ledgers.rs b/crates/socket-patch-core/src/ledgers.rs index 582ca464f..9bcf56623 100644 --- a/crates/socket-patch-core/src/ledgers.rs +++ b/crates/socket-patch-core/src/ledgers.rs @@ -371,6 +371,7 @@ pub fn uuid_only_record(uuid: &str) -> PatchRecord { } } + /// Fold the hosted pins and the vendor ledger's patch records into the /// manifest view update detection consults. Hosted mode records purl→uuid /// ONLY in the lockfiles (`hosted_pins`, uuid only; v5 keeps no hosted diff --git a/crates/socket-patch-core/src/lib.rs b/crates/socket-patch-core/src/lib.rs index c15fe9e64..143eae979 100644 --- a/crates/socket-patch-core/src/lib.rs +++ b/crates/socket-patch-core/src/lib.rs @@ -16,6 +16,7 @@ pub mod utils; pub mod vendor; pub mod vex; + #[cfg(test)] mod golden; #[cfg(test)] diff --git a/crates/socket-patch-core/src/manifest/records.rs b/crates/socket-patch-core/src/manifest/records.rs index 7032d435c..453e0ab2f 100644 --- a/crates/socket-patch-core/src/manifest/records.rs +++ b/crates/socket-patch-core/src/manifest/records.rs @@ -32,10 +32,7 @@ pub fn vulnerabilities_for_manifest( /// `patch`. `files` is the (purl-keyed) before/after-hash map the /// caller built — semantics for what counts as a "patchable file" differ /// between the get and download flows, so the caller owns that decision. -pub fn build_patch_record( - patch: &PatchResponse, - files: HashMap, -) -> PatchRecord { +pub fn build_patch_record(patch: &PatchResponse, files: HashMap) -> PatchRecord { PatchRecord { uuid: patch.uuid.clone(), exported_at: patch.published_at.clone(), diff --git a/crates/socket-patch-core/src/patch/redirect/cargo_lock_equivalence_tests.rs b/crates/socket-patch-core/src/patch/redirect/cargo_lock_equivalence_tests.rs index 5863631df..082849761 100644 --- a/crates/socket-patch-core/src/patch/redirect/cargo_lock_equivalence_tests.rs +++ b/crates/socket-patch-core/src/patch/redirect/cargo_lock_equivalence_tests.rs @@ -97,6 +97,7 @@ fn synth_lock(rng: &mut Rng, blocks: usize, v1: bool) -> String { out } + const INDEX: &str = "sparse+https://socket.example/cargo/index/"; fn plan_new(lock: &str, name: &str, version: &str, cksum: &str) -> CargoLockPlan { @@ -181,8 +182,7 @@ fn span_splice_matches_golden_on_hand_written_locks() { "[root]\nname = \"app\"\nversion = \"0.1.0\"\ndependencies = [\n \"d 1.0.0 ({crates_io})\",\n]\n\n[[package]]\nname = \"d\"\nversion = \"1.0.0\"\nsource = \"{crates_io}\"\n\n[[package]]\nname = \"u\"\nversion = \"2.0.0\"\nsource = \"{crates_io}\"\ndependencies = [\n \"d 1.0.0 ({crates_io})\",\n]\n\n[metadata]\n\"checksum d 1.0.0 ({crates_io})\" = \"cc\"\n\"checksum u 2.0.0 ({crates_io})\" = \"dd\"\n" ); let sourceless_v1 = "[[package]]\nname = \"s\"\nversion = \"1.0.0\"\n\n[metadata]\n\"checksum s 1.0.0 (registry+x)\" = \"ee\"\n".to_string(); - let source_at_eof = - format!("[[package]]\nname = \"e\"\nversion = \"1.0.0\"\nsource = \"{crates_io}\""); + let source_at_eof = format!("[[package]]\nname = \"e\"\nversion = \"1.0.0\"\nsource = \"{crates_io}\""); let bare = "version = 3\n\n[[package]]\nname = \"b\"\nversion = \"1.0.0\"\n\n[[package]]\nname = \"c\"\nversion = \"1.0.0\"\n".to_string(); let mut g = Golden::new( "cargo_lock_hand_written", diff --git a/crates/socket-patch-core/src/patch/redirect/golang_equivalence_tests.rs b/crates/socket-patch-core/src/patch/redirect/golang_equivalence_tests.rs index 075f630b4..3a8ebf5c2 100644 --- a/crates/socket-patch-core/src/patch/redirect/golang_equivalence_tests.rs +++ b/crates/socket-patch-core/src/patch/redirect/golang_equivalence_tests.rs @@ -10,11 +10,7 @@ use super::*; use crate::golden::Golden; use crate::test_rng::Rng; -fn run( - g: &mut Golden, - files: &BTreeMap, - overrides: &[DepOverride], -) -> RewriteResult { +fn run(g: &mut Golden, files: &BTreeMap, overrides: &[DepOverride]) -> RewriteResult { let mut got = RewriteResult::default(); rewrite_golang(files, overrides, &mut got); g.next(&(files, overrides), &got); diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index 19516bdec..72c8b2967 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -48,41 +48,41 @@ mod pdm; mod pipenv; pub mod presence; // The pnpm hosted planner lives with the format's model. -use crate::formats::cargo::hosted::CargoLockPlan; -#[cfg(test)] -use crate::formats::cargo::hosted::CARGO_LOCK_REFERENCE_KIND; +use crate::formats::pnpm::plan_hosted; use crate::formats::cargo::CargoLock; use crate::formats::composer::hosted::rewrite_composer_lock; use crate::formats::gem::gemfile; use crate::formats::gem::hosted::{checksum_entry_span, converge_gem_lock_source}; use crate::formats::gem::lock_lists_direct_dependency; +pub(crate) use crate::formats::yarn::is_berry_lock; +use crate::formats::cargo::hosted::CargoLockPlan; +#[cfg(test)] +use crate::formats::cargo::hosted::CARGO_LOCK_REFERENCE_KIND; #[cfg(test)] use crate::formats::pnpm::hosted::pnpm_unrewritten_instances; -use crate::formats::pnpm::plan_hosted; use crate::formats::yarn::berry_entry::{manifest_bin, render_pinned_entry, Pin}; -pub(crate) use crate::formats::yarn::is_berry_lock; -pub mod gradle; #[cfg(test)] mod pnpm_equivalence_tests; mod poetry; #[cfg(test)] mod python_lock_equivalence_tests; mod requirements; +pub mod gradle; pub use requirements::preflight_requirements_takeover; -pub(crate) mod hosted_url; mod staged; mod state; +pub(crate) mod hosted_url; pub mod upstream; pub mod vlt; pub mod vlt_heal; pub mod vlt_preflight; +pub use state::{ + load_redirect_state, save_redirect_state, + CorruptRedirectState, RedirectState, REDIRECT_STATE_REL, +}; /// Hosted-artifact leaf ownership rule, shared with `vex`'s bun lockfile /// discovery (which recovers a URL tuple's version from that leaf). pub(crate) use hosted_url::{hosted_url_names, hosted_url_version}; -pub use state::{ - load_redirect_state, save_redirect_state, CorruptRedirectState, RedirectState, - REDIRECT_STATE_REL, -}; /// One ecosystem's integrity hashes (mirrors the TS `PatchArtifactIntegrity`). #[derive(Debug, Clone, Default, Deserialize)] @@ -4009,12 +4009,7 @@ fn rewrite_yarn_berry_with_manifests( result.edits.push(FileEdit { path: BERRY_MANIFEST.into(), kind: "redirect_yarn_berry_resolution".into(), - action: if original.is_some() { - "rewritten" - } else { - "added" - } - .into(), + action: if original.is_some() { "rewritten" } else { "added" }.into(), key: Some(selector), original: original.map(Value::String), new: Some(Value::String(dep.artifact_url.clone())), @@ -4256,10 +4251,7 @@ impl BerryResolutionsPin { } let mut changed = Vec::new(); for selector in &self.selectors { - let previous = table - .get(selector) - .and_then(Value::as_str) - .map(str::to_string); + let previous = table.get(selector).and_then(Value::as_str).map(str::to_string); if previous.as_deref() != Some(url) { table.insert(selector.clone(), Value::String(url.to_string())); changed.push((selector.clone(), previous)); @@ -4441,11 +4433,7 @@ fn berry_catalog_selectors(yarnrc: Option<&str>, name: &str, ranges: &[&str]) -> /// order before the edit (`was_sorted`, from [`berry_entries_sorted`]; a /// hand-edited lock) keeps the entry in place, so a pin and its rollback /// still round-trip byte-exactly. -pub(crate) fn berry_reposition_blocks( - blocks: &mut Vec, - moved: &[String], - was_sorted: bool, -) { +pub(crate) fn berry_reposition_blocks(blocks: &mut Vec, moved: &[String], was_sorted: bool) { if !was_sorted { return; } @@ -4470,6 +4458,7 @@ pub(crate) fn berry_reposition_blocks( } } + // ── bun.lock (text lockfile) ───────────────────────────────────────────────── // A registry 4-tuple `["name@version", "", {deps}, "sha512-…"]` is // rewritten to a URL 3-tuple `["name@", {deps verbatim}, @@ -5070,6 +5059,7 @@ fn rewrite_uv_lock( } } + // ── composer.lock ──────────────────────────────────────────────────────────── /// Whether `text` points at `artifact_url` in any spelling a rewritten file may /// carry: the raw url every rewriter emits — composer.lock included, since @@ -8079,10 +8069,7 @@ mod tests { let files = BTreeMap::from([("nuget.config".into(), config)]); let result = rewrite_registry_redirect(&files, &[nuget_override()]); let out = result.files.get("nuget.config").expect("config rewritten"); - assert!( - out.contains(&source), - "original source bytes preserved: {out}" - ); + assert!(out.contains(&source), "original source bytes preserved: {out}"); // XML normalizes literal attribute whitespace to spaces, but // preserves character references. The fallback must keep the // same source identity under a real XML reader, not just ours. @@ -8639,11 +8626,7 @@ mod tests { #[test] fn yarn_berry_hosted_pin_routes_resolutions_to_a_tarball_entry() { let checksum = format!("10c0/{}", "7".repeat(128)); - let scoped_url = berry_hosted_url( - "@isaacs/string-locale-compare", - "string-locale-compare", - "1.1.0", - ); + let scoped_url = berry_hosted_url("@isaacs/string-locale-compare", "string-locale-compare", "1.1.0"); let plain_url = berry_hosted_url("left-pad", "left-pad", "1.3.0"); let scoped = DepOverride { namespace: Some("@isaacs".into()), @@ -8676,14 +8659,8 @@ mod tests { )), "unscoped entry re-keyed to its tarball: {out}" ); - assert!( - !out.contains("__archiveUrl") && !out.contains("@npm:"), - "{out}" - ); - assert!( - out.ends_with("linkType: hard\n"), - "trailing newline kept: {out:?}" - ); + assert!(!out.contains("__archiveUrl") && !out.contains("@npm:"), "{out}"); + assert!(out.ends_with("linkType: hard\n"), "trailing newline kept: {out:?}"); let manifest: Value = serde_json::from_str(&r.files["package.json"]).unwrap(); assert_eq!( manifest["resolutions"], @@ -8695,17 +8672,11 @@ mod tests { ); assert_eq!(manifest["name"], "app", "the rest of the manifest is kept"); assert_eq!( - r.edits - .iter() - .filter(|e| e.kind == "redirect_yarn_berry_entry") - .count(), + r.edits.iter().filter(|e| e.kind == "redirect_yarn_berry_entry").count(), 2 ); assert_eq!( - r.edits - .iter() - .filter(|e| e.kind == "redirect_yarn_berry_resolution") - .count(), + r.edits.iter().filter(|e| e.kind == "redirect_yarn_berry_resolution").count(), 2 ); } @@ -8938,7 +8909,10 @@ mod tests { rewrite_yarn_berry(&files, std::slice::from_ref(&ovr), &mut r); assert!(r.warnings.is_empty(), "{:?}", r.warnings); let out = &r.files["yarn.lock"]; - let keys: Vec<&str> = out.lines().filter(|l| l.starts_with('"')).collect(); + let keys: Vec<&str> = out + .lines() + .filter(|l| l.starts_with('"')) + .collect(); assert_eq!( keys, vec![ @@ -8982,11 +8956,7 @@ mod tests { let mut again = RewriteResult::default(); rewrite_yarn_berry(&pinned, std::slice::from_ref(&ovr), &mut again); assert!(again.warnings.is_empty(), "{:?}", again.warnings); - assert!( - again.files.is_empty(), - "repeat run rewrites nothing: {:?}", - again.files - ); + assert!(again.files.is_empty(), "repeat run rewrites nothing: {:?}", again.files); // A pin already complete is confirmed without a write. assert!(again.confirmed_yarn_berry_uuids.contains(BERRY_UUID)); @@ -8999,10 +8969,7 @@ mod tests { assert!(out.contains(&format!("\"left-pad@{new_url}\":")), "{out}"); assert!(!out.contains(BERRY_UUID), "{out}"); let manifest: Value = serde_json::from_str(&repin.files["package.json"]).unwrap(); - assert_eq!( - manifest["resolutions"], - json!({"left-pad@npm:^1.3.0": new_url}) - ); + assert_eq!(manifest["resolutions"], json!({"left-pad@npm:^1.3.0": new_url})); } /// The URL-keyed lock entry alone is half a pin: with its manifest @@ -9249,9 +9216,7 @@ mod tests { assert!(r.warnings.is_empty(), "{:?}", r.warnings); let out = &r.files["yarn.lock"]; assert!( - out.contains(&format!( - "\"left-pad@{url}\":\n version: 1.3.0\n resolution: \"left-pad@{url}\"\n" - )), + out.contains(&format!("\"left-pad@{url}\":\n version: 1.3.0\n resolution: \"left-pad@{url}\"\n")), "{out}" ); assert!(!out.contains("__archiveUrl"), "{out}"); @@ -9277,17 +9242,10 @@ mod tests { "{{\n \"name\": \"app\",\n \"resolutions\": {{\n \"{selector}\": \"1.3.0\"\n }}\n}}\n" ); let mut r = RewriteResult::default(); - rewrite_yarn_berry( - &berry_files(berry_lock("10c0"), manifest), - std::slice::from_ref(&ovr), - &mut r, - ); + rewrite_yarn_berry(&berry_files(berry_lock("10c0"), manifest), std::slice::from_ref(&ovr), &mut r); assert!(r.files.is_empty(), "{label}: {:?}", r.files); assert_eq!( - r.warnings - .iter() - .map(|w| w.code.as_str()) - .collect::>(), + r.warnings.iter().map(|w| w.code.as_str()).collect::>(), vec!["redirect_yarn_berry_resolutions_conflict"], "{label}" ); @@ -9312,20 +9270,12 @@ mod tests { "mirror tarball" ); // An unrelated user entry is kept as-is next to ours. - let manifest = - "{\n \"name\": \"app\",\n \"resolutions\": {\n \"other\": \"2.0.0\"\n }\n}\n"; + let manifest = "{\n \"name\": \"app\",\n \"resolutions\": {\n \"other\": \"2.0.0\"\n }\n}\n"; let mut r = RewriteResult::default(); - rewrite_yarn_berry( - &berry_files(berry_lock("10c0"), manifest.into()), - std::slice::from_ref(&ovr), - &mut r, - ); + rewrite_yarn_berry(&berry_files(berry_lock("10c0"), manifest.into()), std::slice::from_ref(&ovr), &mut r); assert!(r.warnings.is_empty(), "{:?}", r.warnings); let m: Value = serde_json::from_str(&r.files["package.json"]).unwrap(); - assert_eq!( - m["resolutions"], - json!({"other": "2.0.0", "left-pad@npm:^1.3.0": url}) - ); + assert_eq!(m["resolutions"], json!({"other": "2.0.0", "left-pad@npm:^1.3.0": url})); let mut files = BTreeMap::new(); files.insert("yarn.lock".to_string(), berry_lock("10c0")); @@ -9333,10 +9283,7 @@ mod tests { rewrite_yarn_berry(&files, std::slice::from_ref(&ovr), &mut r); assert!(r.files.is_empty(), "{:?}", r.files); assert_eq!( - r.warnings - .iter() - .map(|w| w.code.as_str()) - .collect::>(), + r.warnings.iter().map(|w| w.code.as_str()).collect::>(), vec!["redirect_yarn_berry_manifest_missing"] ); @@ -9347,17 +9294,10 @@ mod tests { berry_lock("10c0") ); let mut r = RewriteResult::default(); - rewrite_yarn_berry( - &berry_files(with_patch, berry_manifest()), - std::slice::from_ref(&ovr), - &mut r, - ); + rewrite_yarn_berry(&berry_files(with_patch, berry_manifest()), std::slice::from_ref(&ovr), &mut r); assert!(r.files.is_empty(), "{:?}", r.files); let codes: Vec<&str> = r.warnings.iter().map(|w| w.code.as_str()).collect(); - assert!( - codes.contains(&"redirect_yarn_berry_shared_descriptor"), - "{codes:?}" - ); + assert!(codes.contains(&"redirect_yarn_berry_shared_descriptor"), "{codes:?}"); } /// Yarn routes a URL locator to its tarball fetcher only when it is an @@ -9382,10 +9322,7 @@ mod tests { assert!(r.files.is_empty(), "{url}: nothing written"); assert!(r.edits.is_empty(), "{url}: {:?}", r.edits); assert_eq!( - r.warnings - .iter() - .map(|w| w.code.as_str()) - .collect::>(), + r.warnings.iter().map(|w| w.code.as_str()).collect::>(), vec!["redirect_yarn_berry_artifact_url_unsupported"], "{url}" ); @@ -12649,11 +12586,7 @@ mod tests { let out = r.files.get("Gemfile.lock").expect("lock rewritten"); let rows: Vec<&str> = out .lines() - .filter(|l| { - l.trim_start().starts_with("rails (7.0.0)") - && l.starts_with(" ") - && !l.starts_with(" ") - }) + .filter(|l| l.trim_start().starts_with("rails (7.0.0)") && l.starts_with(" ") && !l.starts_with(" ")) .collect(); assert_eq!( rows, @@ -12666,11 +12599,7 @@ mod tests { "{entry}: the entry keeps its line ending: {out:?}" ); let model = crate::formats::gem::GemfileLock::parse(out); - assert_eq!( - model.checksum("rails", "7.0.0"), - Some(patched.as_str()), - "{entry}" - ); + assert_eq!(model.checksum("rails", "7.0.0"), Some(patched.as_str()), "{entry}"); assert!(!out.contains("\r\r"), "line endings kept: {out:?}"); let edit = r .edits @@ -12685,10 +12614,7 @@ mod tests { files.insert("Gemfile.lock".to_string(), out.clone()); let again = rewrite_registry_redirect(&files, &[gem_override("rails", "7.0.0")]); assert!( - !again - .edits - .iter() - .any(|e| e.kind == "redirect_gemfile_lock_checksum"), + !again.edits.iter().any(|e| e.kind == "redirect_gemfile_lock_checksum"), "{entry}: rerun is a no-op: {:?}", again.edits ); @@ -13192,19 +13118,11 @@ mod tests { let redacted = format!( "https://patch.socket.dev/patch/npm/left-pad/1.3.0//{uuid}/left-pad-1.3.0.tgz?x=1" ); - assert_eq!( - redact_grant_token(&url, &url, uuid), - redacted, - "the URL alone" - ); + assert_eq!(redact_grant_token(&url, &url, uuid), redacted, "the URL alone"); let text = format!("vlt would fail to verify {url}: fetch error GET {url}: reset"); - let want = - format!("vlt would fail to verify {redacted}: fetch error GET {redacted}: reset"); + let want = format!("vlt would fail to verify {redacted}: fetch error GET {redacted}: reset"); assert_eq!(redact_grant_token(&text, &url, uuid), want, "every quote"); - assert!( - !redact_grant_token(&text, &url, uuid).contains(token), - "no token left" - ); + assert!(!redact_grant_token(&text, &url, uuid).contains(token), "no token left"); let registry = format!("https://patch.socket.dev/patch-registry/npm/{token}/{uuid}"); assert_eq!( redact_grant_token(®istry, ®istry, uuid), @@ -14959,10 +14877,7 @@ mod tests { ("crlf", lf.replace('\n', "\r\n")), ("tabs", lf.replace(" ", "\t")), ("bom", format!("\u{feff}{lf}")), - ( - "bom+crlf+tabs", - format!("\u{feff}{}", lf.replace(" ", "\t").replace('\n', "\r\n")), - ), + ("bom+crlf+tabs", format!("\u{feff}{}", lf.replace(" ", "\t").replace('\n', "\r\n"))), ]; for (shape, pristine) in shapes { let mut files = BTreeMap::new(); @@ -14978,10 +14893,7 @@ mod tests { "http://patch.test/left-pad-1.3.0.tgz", ) .replace("sha512-UPSTREAM==", "sha512-PATCHED=="); - assert_eq!( - out, &expected, - "{shape}: only the rewired values may change" - ); + assert_eq!(out, &expected, "{shape}: only the rewired values may change"); } } @@ -17325,8 +17237,7 @@ packages: ); // One edit; its fragments are the on-disk bytes of the entry. - let lock_edits: Vec<&FileEdit> = - r.edits.iter().filter(|e| e.path == "yarn.lock").collect(); + let lock_edits: Vec<&FileEdit> = r.edits.iter().filter(|e| e.path == "yarn.lock").collect(); assert_eq!(lock_edits.len(), 1, "{label}"); let edit = lock_edits[0]; let (orig, new) = ( @@ -17336,8 +17247,15 @@ packages: assert_eq!( (orig, new), ( - respell(lf_edit.original.as_ref().unwrap().as_str().unwrap()) - .trim_start_matches('\u{feff}'), + respell( + lf_edit + .original + .as_ref() + .unwrap() + .as_str() + .unwrap() + ) + .trim_start_matches('\u{feff}'), respell(lf_edit.new.as_ref().unwrap().as_str().unwrap()) .trim_start_matches('\u{feff}'), ), @@ -19408,11 +19326,7 @@ packages: format!( "__metadata:\n version: 8\n cacheKey: 10c0\n\n{key}:\n version: 9.0.1\n \ resolution: \"x\"\n{} languageName: node\n linkType: hard\n", - if bin { - " bin:\n uuid: dist/bin/uuid\n" - } else { - "" - } + if bin { " bin:\n uuid: dist/bin/uuid\n" } else { "" } ) }; let needs = |lock: String| berry_pin_needs_manifest(&berry_bin_entries(&lock), &dep); @@ -19423,14 +19337,9 @@ packages: assert!(!needs(entry("\"uuid@npm:other-uuid@^9.0.0\"", true))); assert!(!needs(entry("\"uuid@npm:^9.0.0, other@npm:^1.0.0\"", true))); assert!(!needs(entry("\"uuid@patch:uuid@npm%3A9.0.1#x\"", true))); - assert!(!needs(entry( - "\"uuid@https://mirror.example/uuid-9.0.1.tgz\"", - true - ))); + assert!(!needs(entry("\"uuid@https://mirror.example/uuid-9.0.1.tgz\"", true))); // Another version of the package (`9.0.10` shares the prefix). - assert!(!needs( - entry("\"uuid@npm:^9.0.0\"", true).replace("9.0.1\n", "9.0.10\n") - )); + assert!(!needs(entry("\"uuid@npm:^9.0.0\"", true).replace("9.0.1\n", "9.0.10\n"))); } /// A bun URL 3-tuple already at the CURRENT artifact URL but with a stale diff --git a/crates/socket-patch-core/src/patch/redirect/npmrc.rs b/crates/socket-patch-core/src/patch/redirect/npmrc.rs index 6a9c4a17a..ac102ef78 100644 --- a/crates/socket-patch-core/src/patch/redirect/npmrc.rs +++ b/crates/socket-patch-core/src/patch/redirect/npmrc.rs @@ -33,6 +33,8 @@ //! and — when the project file is silent — the user / global / builtin //! config files ([`resolve_outer_allow_remote`]). + + /// Repo-relative path of the project `.npmrc` the auto-config edits. pub const NPMRC_REL: &str = ".npmrc"; @@ -1135,4 +1137,5 @@ mod tests { ); } } + } diff --git a/crates/socket-patch-core/src/patch/redirect/pdm.rs b/crates/socket-patch-core/src/patch/redirect/pdm.rs index 3cb7054c4..0589fa780 100644 --- a/crates/socket-patch-core/src/patch/redirect/pdm.rs +++ b/crates/socket-patch-core/src/patch/redirect/pdm.rs @@ -269,18 +269,9 @@ mod tests { #[test] fn legacy_formats_warn_stale_install_risk_once() { for (fixture, warns) in [ - ( - include_str!("../../../tests/fixtures/pdm-native/0.12.3.lock"), - true, - ), - ( - include_str!("../../../tests/fixtures/pdm-native/2.8.2.lock"), - true, - ), - ( - include_str!("../../../tests/fixtures/pdm-native/2.29.2.lock"), - false, - ), + (include_str!("../../../tests/fixtures/pdm-native/0.12.3.lock"), true), + (include_str!("../../../tests/fixtures/pdm-native/2.8.2.lock"), true), + (include_str!("../../../tests/fixtures/pdm-native/2.29.2.lock"), false), ] { let mut result = RewriteResult::default(); rewrite( @@ -453,11 +444,7 @@ mod parse_reuse_equivalence_tests { let what = format!("{fixture} extra={extra} crlf={crlf}"); let mut got = RewriteResult::default(); rewrite(&files, &deps, &mut got); - g.case( - what.replace(' ', "/"), - &(&files, &deps), - &format!("{got:?}"), - ); + g.case(what.replace(' ', "/"), &(&files, &deps), &format!("{got:?}")); confirmed += got.confirmed_pdm_uuids.len(); let mut again = files.clone(); diff --git a/crates/socket-patch-core/src/patch/redirect/pipenv.rs b/crates/socket-patch-core/src/patch/redirect/pipenv.rs index c13b4a567..0371ec923 100644 --- a/crates/socket-patch-core/src/patch/redirect/pipenv.rs +++ b/crates/socket-patch-core/src/patch/redirect/pipenv.rs @@ -456,10 +456,7 @@ mod tests { let original = serde_json::to_string(&value).unwrap(); // A live lock (Pipfile beside it): conflicts veto the siblings. let files = BTreeMap::from([ - ( - "Pipfile".to_string(), - "[packages]\nurllib3 = \"*\"\n".to_string(), - ), + ("Pipfile".to_string(), "[packages]\nurllib3 = \"*\"\n".to_string()), ("Pipfile.lock".to_string(), original), ]); let mut result = RewriteResult::default(); @@ -499,30 +496,20 @@ mod tests { for stale in &stale_locks { let files = BTreeMap::from([ ("Pipfile.lock".to_string(), stale.clone()), - ( - "requirements.txt".to_string(), - "urllib3==1.26.18\n".to_string(), - ), + ("requirements.txt".to_string(), "urllib3==1.26.18\n".to_string()), ]); - let result = - super::super::rewrite_registry_redirect(&files, std::slice::from_ref(&dep)); + let result = super::super::rewrite_registry_redirect(&files, std::slice::from_ref(&dep)); assert!( !result.refused_pipenv_uuids.contains("patch-one"), "a non-conflict must not veto: {stale}" ); assert!( - result - .warnings - .iter() - .any(|w| w.code == "redirect_pipenv_skipped"), + result.warnings.iter().any(|w| w.code == "redirect_pipenv_skipped"), "{:?}", result.warnings ); assert!( - result - .files - .get("requirements.txt") - .is_some_and(|t| t.contains("patch.socket.dev")), + result.files.get("requirements.txt").is_some_and(|t| t.contains("patch.socket.dev")), "requirements.txt must still be redirected past a stale Pipfile.lock: {result:?}" ); assert!(!result.files.contains_key("Pipfile.lock")); @@ -580,10 +567,7 @@ mod tests { let files = |text: &str| BTreeMap::from([("Pipfile.lock".to_string(), text.to_string())]); assert!(lock_targets(&files(&lock()), std::slice::from_ref(&dep))); assert!(!lock_targets(&files(&lock()), std::slice::from_ref(&other))); - assert!(!lock_targets( - &files("{ not json"), - std::slice::from_ref(&dep) - )); + assert!(!lock_targets(&files("{ not json"), std::slice::from_ref(&dep))); assert!(!lock_targets(&BTreeMap::new(), std::slice::from_ref(&dep))); let mut npm = dep.clone(); npm.ecosystem = "npm".into(); @@ -609,10 +593,7 @@ mod tests { let entry: Value = serde_json::from_str(&fixed).unwrap(); assert!(entry["default"]["urllib3"].get("version").is_none()); assert_eq!(entry["default"]["urllib3"]["index"], json!("pypi")); - assert!(entry["default"]["urllib3"]["file"] - .as_str() - .unwrap() - .contains("patch-one")); + assert!(entry["default"]["urllib3"]["file"].as_str().unwrap().contains("patch-one")); value["default"]["urllib3"]["version"] = json!("==2.0.0"); let conflicting = serde_json::to_string(&value).unwrap(); @@ -633,10 +614,7 @@ mod tests { assert!(owned_url(public, &dep)); assert!(!owned_url("https://example.org/patch/pypi/urllib3/1.26.18/tok/patch-one/urllib3-1.26.18-py3-none-any.whl", &dep)); dep.artifact_url = "https://patches.internal.example:8443/patch/pypi/urllib3/1.26.18/tok/patch-one/urllib3-1.26.18-py3-none-any.whl".into(); - assert!( - owned_url(&dep.artifact_url, &dep), - "the grant's own origin is ours" - ); + assert!(owned_url(&dep.artifact_url, &dep), "the grant's own origin is ours"); assert!(owned_url(public, &dep), "and so is the public service"); assert!(!owned_url("https://patches.internal.example:8443/patch/pypi/urllib3/1.26.19/tok/patch-one/urllib3-1.26.19-py3-none-any.whl", &dep), "another version is not"); // Rotation on the custom origin re-points the owned entry. @@ -647,6 +625,7 @@ mod tests { assert!(second.contains("/rotated/") && !second.contains("/tok/")); } + /// Hosted Pipenv recognizes its own pins through the shared recognizer /// (#563): a path-prefixed `--patch-server-url` deployment rotates its /// grant instead of refusing its own previous reference, and a hosted @@ -720,9 +699,7 @@ mod compatibility_tests { assert!(!result.refused_pipenv_uuids.contains("patch-one")); assert!(result.files["requirements.txt"].contains("patch.socket.dev")); assert!(!result.files.contains_key("Pipfile.lock")); - assert!(result - .warnings - .iter() - .any(|w| w.code == "redirect_pipenv_refused" && w.detail.contains("no Pipfile"))); + assert!(result.warnings.iter().any(|w| w.code == "redirect_pipenv_refused" && w.detail.contains("no Pipfile"))); } + } diff --git a/crates/socket-patch-core/src/patch/redirect/poetry.rs b/crates/socket-patch-core/src/patch/redirect/poetry.rs index 5ee901ff6..c9826d935 100644 --- a/crates/socket-patch-core/src/patch/redirect/poetry.rs +++ b/crates/socket-patch-core/src/patch/redirect/poetry.rs @@ -89,9 +89,7 @@ pub(super) fn rewrite_poetry( new: Some(Value::String(new)), }); } - result - .confirmed_python_lock_uuids - .insert(dep.patch_uuid.clone()); + result.confirmed_python_lock_uuids.insert(dep.patch_uuid.clone()); if !stale_warned { if let Some(format) = *writer_format.get_or_insert_with(|| pre_1_4_writer(&content)) @@ -126,18 +124,14 @@ pub(super) fn rewrite_poetry( } // Already redirected to this artifact (idempotent re-scan). LockStep::Unchanged => { - result - .confirmed_python_lock_uuids - .insert(dep.patch_uuid.clone()); + result.confirmed_python_lock_uuids.insert(dep.patch_uuid.clone()); } LockStep::NotFound => result.warnings.push(RewriteWarning { code: "redirect_poetry_entry_not_found".into(), detail: format!("no {path} entry for {}@{}", dep.name, dep.version), }), LockStep::Refused(detail) => { - result - .refused_python_lock_uuids - .insert(dep.patch_uuid.clone()); + result.refused_python_lock_uuids.insert(dep.patch_uuid.clone()); result.warnings.push(RewriteWarning { code: "redirect_poetry_lock_unsupported".into(), detail: format!("{path}: {detail}"), @@ -262,11 +256,7 @@ mod equivalence_tests { let mut again = files.clone(); again.extend(got.files.clone()); let got = run(rewrite_poetry, &again, &deps); - g.case( - format!("{what}/re-run"), - &(&again, &deps), - &format!("{got:?}"), - ); + g.case(format!("{what}/re-run"), &(&again, &deps), &format!("{got:?}")); } } } diff --git a/crates/socket-patch-core/src/patch/redirect/state.rs b/crates/socket-patch-core/src/patch/redirect/state.rs index 98d0b620e..6d1b2f5d0 100644 --- a/crates/socket-patch-core/src/patch/redirect/state.rs +++ b/crates/socket-patch-core/src/patch/redirect/state.rs @@ -56,6 +56,7 @@ impl RedirectState { records: BTreeMap::new(), } } + } impl Default for RedirectState { @@ -517,4 +518,5 @@ mod tests { "changed bytes still go through the (here refused) atomic write" ); } + } diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/bun_lockb.rs b/crates/socket-patch-core/src/patch/redirect/upstream/bun_lockb.rs index 87c49a542..f51142f69 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/bun_lockb.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/bun_lockb.rs @@ -332,10 +332,7 @@ mod tests { let package_start = u64::from_le_bytes(lock[110..118].try_into().unwrap()) as usize; // The root resolution's flag byte (its last). let flags_at = package_start + count * 16 + 63; - assert_eq!( - lock[flags_at], - crate::vendor::bun_lockb::NORMALIZED_FORMAT_1 - ); + assert_eq!(lock[flags_at], crate::vendor::bun_lockb::NORMALIZED_FORMAT_1); lock[flags_at] |= 0x40; BunLockb::parse(&lock).unwrap().validate_mutation().unwrap(); let (outcome, after) = run(&lock, &vendor_opts()).await; diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs b/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs index 70ca86a6d..c44d7919e 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs @@ -97,10 +97,7 @@ pub(crate) async fn restore( ) }); let cksums: BTreeMap> = - futures_util::future::join_all(lookups) - .await - .into_iter() - .collect(); + futures_util::future::join_all(lookups).await.into_iter().collect(); let mut changed = false; let mut restored: Vec<(&LockHit, String)> = Vec::new(); for hit in &hits { @@ -119,9 +116,7 @@ pub(crate) async fn restore( } // The entries' own source + checksum values, spliced at the parse's // spans (every hit is a distinct block: its source names its uuid). - let spans = model - .spans() - .expect("a lock parsed from text carries spans"); + let spans = model.spans().expect("a lock parsed from text carries spans"); let mut splices: Vec<(std::ops::Range, String)> = Vec::new(); for (hit, cksum) in &restored { let at = &spans.packages[hit.index]; @@ -138,10 +133,7 @@ pub(crate) async fn restore( } for (hit, cksum) in &restored { // Dependents' full-id references and the v1 `[metadata]` key. - lock = lock.replace( - &format!("({})", hit.source), - &format!("({CRATES_IO_SOURCE})"), - ); + lock = lock.replace(&format!("({})", hit.source), &format!("({CRATES_IO_SOURCE})")); let metadata_key = format!( "\"checksum {} {} ({CRATES_IO_SOURCE})\" = \"", hit.name, hit.version @@ -160,11 +152,7 @@ pub(crate) async fn restore( if changed { view.write( "Cargo.lock", - if crlf { - lock.replace('\n', "\r\n") - } else { - lock - }, + if crlf { lock.replace('\n', "\r\n") } else { lock }, ); } } @@ -329,10 +317,11 @@ fn remove_registry_block(config: &str, reg: &str) -> Option { end -= 1; } let fragment = format!("{}\n", lines[i..end].join("\n")); - let removed = remove_appended_cargo_block(&lf, &fragment).or_else(|| { - // The block ends the file with no final newline. - remove_appended_cargo_block(&lf, fragment.trim_end_matches('\n')) - })?; + let removed = remove_appended_cargo_block(&lf, &fragment) + .or_else(|| { + // The block ends the file with no final newline. + remove_appended_cargo_block(&lf, fragment.trim_end_matches('\n')) + })?; Some(if crlf { removed.replace('\n', "\r\n") } else { @@ -399,10 +388,7 @@ mod tests { #[test] fn table_form_line_is_dropped() { - assert_eq!( - unpin_line(&format!("registry = \"{REG}\""), REG), - Some(None) - ); + assert_eq!(unpin_line(&format!("registry = \"{REG}\""), REG), Some(None)); } #[test] @@ -411,10 +397,7 @@ mod tests { let hosted = format!( "{original}\n[registries.{REG}]\nindex = \"sparse+https://patch.socket.dev/x/index/\"\n" ); - assert_eq!( - remove_registry_block(&hosted, REG).as_deref(), - Some(original) - ); + assert_eq!(remove_registry_block(&hosted, REG).as_deref(), Some(original)); let created = format!("[registries.{REG}]\nindex = \"sparse+https://x/\"\n"); assert_eq!(remove_registry_block(&created, REG).as_deref(), Some("")); } diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/gem.rs b/crates/socket-patch-core/src/patch/redirect/upstream/gem.rs index c47227d7e..a20a3cb3c 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/gem.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/gem.rs @@ -57,11 +57,11 @@ use std::collections::{BTreeMap, BTreeSet}; use regex::Regex; use super::{Ctx, FormatResult, HostedPin, View}; +use crate::utils::line_endings::{to_lf, LineEndings}; +use crate::vendor::gem::{gem_declaration_any, quoted_literal}; use crate::formats::gem::{ bundler_manifest_for, parse_spec, same_remote, split_checksum_entry, BUNDLER_LOCKS, }; -use crate::utils::line_endings::{to_lf, LineEndings}; -use crate::vendor::gem::{gem_declaration_any, quoted_literal}; /// The default upstream `GEM` remote. const RUBYGEMS_REMOTE: &str = "https://rubygems.org/"; @@ -250,14 +250,17 @@ fn choose_upstream( /// The line after which a spec named `name-version` sorts into `sec` /// (bundler writes specs sorted by full name). fn insertion_point(sec: &GemSec, full_name: &str) -> Option { - let pred = sec.entries.iter().rfind(|e| { - let full = if e.version.is_empty() { - e.name.clone() - } else { - format!("{}-{}", e.name, e.version) - }; - full.as_str() < full_name - }); + let pred = sec + .entries + .iter() + .rfind(|e| { + let full = if e.version.is_empty() { + e.name.clone() + } else { + format!("{}-{}", e.name, e.version) + }; + full.as_str() < full_name + }); pred.map(|e| e.last).or(sec.specs_line) } diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/golang.rs b/crates/socket-patch-core/src/patch/redirect/upstream/golang.rs index cee422040..4ce16051f 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/golang.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/golang.rs @@ -65,10 +65,7 @@ pub(crate) async fn restore( (uuid.clone(), ctx.client.go_sums(module, version).await) }); let sums: std::collections::BTreeMap> = - futures_util::future::join_all(lookups) - .await - .into_iter() - .collect(); + futures_util::future::join_all(lookups).await.into_iter().collect(); let mut go_mod_next = go_mod.clone(); let mut go_sum = view.read("go.sum").await.ok().flatten(); @@ -91,8 +88,8 @@ pub(crate) async fn restore( } } if let Some(text) = go_sum.as_deref() { - let mut next = - remove_module_prefix_lines(text, socket_module).unwrap_or_else(|| text.to_string()); + let mut next = remove_module_prefix_lines(text, socket_module) + .unwrap_or_else(|| text.to_string()); let upstream = format!( "{module} {version} {}\n{module} {version}/go.mod {}\n", sums.zip_h1, sums.mod_h1 diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs b/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs index 3de39ee92..8417b5a8b 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs @@ -350,7 +350,9 @@ pub struct RestoreOutcome { impl RestoreOutcome { pub fn restored(&self) -> impl Iterator { - self.pins.iter().filter(|p| p.status == PinStatus::Restored) + self.pins + .iter() + .filter(|p| p.status == PinStatus::Restored) } pub fn refused(&self) -> impl Iterator { @@ -682,7 +684,9 @@ async fn restore_pass(view: &mut View<'_>, active: &[&HostedPin], ctx: &Ctx<'_>) Format::YarnLock => npm::restore_yarn_locks(view, &pins, &files, ctx).await, Format::PnpmLock => npm::restore_pnpm_locks(view, &pins, &files, ctx).await, Format::BunLock => npm::restore_bun_locks(view, &pins, &files, ctx).await, - Format::BunLockb if ctx.bun_lockb => bun_lockb::restore(view, &pins, &files, ctx).await, + Format::BunLockb if ctx.bun_lockb => { + bun_lockb::restore(view, &pins, &files, ctx).await + } Format::Cargo => cargo::restore(view, &pins, &files, ctx).await, Format::Golang => golang::restore(view, &pins, &files, ctx).await, Format::Gem => gem::restore(view, &pins, &files, ctx).await, diff --git a/crates/socket-patch-core/src/patch/redirect/upstream/pypi_locks.rs b/crates/socket-patch-core/src/patch/redirect/upstream/pypi_locks.rs index 8530ddf48..ac105569f 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/pypi_locks.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/pypi_locks.rs @@ -57,16 +57,7 @@ fn files_value(release: &[PypiFile], by_url: bool) -> Option { let key = if by_url { "url" } else { "file" }; let mut located: Vec<(&str, &PypiFile)> = release .iter() - .map(|f| { - ( - if by_url { - f.url.as_str() - } else { - f.filename.as_str() - }, - f, - ) - }) + .map(|f| (if by_url { f.url.as_str() } else { f.filename.as_str() }, f)) .collect(); // PDM orders each entry's files by the location it writes: a `static_urls` // lock by URL (so an sdist under `0c/…` precedes a wheel under `b0/…`), diff --git a/crates/socket-patch-core/src/policy/mod.rs b/crates/socket-patch-core/src/policy/mod.rs index 66fb3c863..4e1fe9479 100644 --- a/crates/socket-patch-core/src/policy/mod.rs +++ b/crates/socket-patch-core/src/policy/mod.rs @@ -454,8 +454,7 @@ fn compile(file: &str, lists: &[(&'static str, &[String])]) -> Result &'static SelectionPolicy { - static DEFAULTS: std::sync::LazyLock = - std::sync::LazyLock::new(SelectionPolicy::unrestricted); + static DEFAULTS: std::sync::LazyLock = std::sync::LazyLock::new(SelectionPolicy::unrestricted); &DEFAULTS } @@ -804,9 +803,7 @@ fn ceiling_dirs() -> Vec { #[cfg(unix)] fn trusted_owner(meta: &std::fs::Metadata) -> bool { use std::os::unix::fs::MetadataExt; - let sudo_uid = std::env::var("SUDO_UID") - .ok() - .and_then(|v| v.trim().parse::().ok()); + let sudo_uid = std::env::var("SUDO_UID").ok().and_then(|v| v.trim().parse::().ok()); // SAFETY: geteuid has no preconditions and cannot fail. owner_trusted(meta.uid(), unsafe { libc::geteuid() }, sudo_uid) } diff --git a/crates/socket-patch-core/src/policy/report.rs b/crates/socket-patch-core/src/policy/report.rs index 0d095c7dc..ba8ff4221 100644 --- a/crates/socket-patch-core/src/policy/report.rs +++ b/crates/socket-patch-core/src/policy/report.rs @@ -48,9 +48,7 @@ pub fn policy_block( let (floor, floor_source) = policy.min_severity(); // Sorted: crawl order is filesystem order, and the two engines differ. let mut filtered: Vec<&FilteredEntry> = filtered.iter().collect(); - filtered.sort_by(|a, b| { - (&a.project, &a.purl, a.reason.code()).cmp(&(&b.project, &b.purl, b.reason.code())) - }); + filtered.sort_by(|a, b| (&a.project, &a.purl, a.reason.code()).cmp(&(&b.project, &b.purl, b.reason.code()))); let mut retained: Vec<&RetainedEntry> = retained.iter().collect(); retained.sort_by(|a, b| (&a.project, &a.purl).cmp(&(&b.project, &b.purl))); let filtered: Vec = filtered diff --git a/crates/socket-patch-core/src/policy/socket_yml.rs b/crates/socket-patch-core/src/policy/socket_yml.rs index 103fe20bd..30a99499c 100644 --- a/crates/socket-patch-core/src/policy/socket_yml.rs +++ b/crates/socket-patch-core/src/policy/socket_yml.rs @@ -486,11 +486,7 @@ pub(crate) fn package_spec_error(spec: &str) -> Option<&'static str> { if spec.is_empty() { return Some("package spec is empty"); } - if let Some(rest) = spec - .get(..4) - .filter(|p| p.eq_ignore_ascii_case("pkg:")) - .map(|_| &spec[4..]) - { + if let Some(rest) = spec.get(..4).filter(|p| p.eq_ignore_ascii_case("pkg:")).map(|_| &spec[4..]) { let valid = rest.split_once('/').is_some_and(|(ty, name)| { !ty.is_empty() && !name.trim_matches('/').is_empty() && !name.starts_with('@') }); @@ -789,9 +785,7 @@ pub(crate) fn parse_file( Some(Err((key, message))) => { warnings.push(PolicyWarning { code: super::SOCKET_YML_IGNORED_VALUE, - detail: super::strip_unsafe(&format!( - "{file}: {key} {message}; the key is ignored" - )), + detail: super::strip_unsafe(&format!("{file}: {key} {message}; the key is ignored")), }); Vec::new() } @@ -922,12 +916,8 @@ mod tests { // YAML beats everything; the case variant beats the version gate; // the version gate beats the keys. assert_eq!(err_key("patches: {minSeverty: x}\n").0, "version"); - assert!(err_key("Patches: {}\npatches: {minSeverty: x}\n") - .1 - .contains("misspelled")); - assert!(err_key("patches: {minSeverty: x\n") - .1 - .contains("invalid YAML")); + assert!(err_key("Patches: {}\npatches: {minSeverty: x}\n").1.contains("misspelled")); + assert!(err_key("patches: {minSeverty: x\n").1.contains("invalid YAML")); } #[test] @@ -996,9 +986,12 @@ mod tests { let (key, message) = err_key(text); assert_eq!(key, "", "{text:?}"); assert!( - ["invalid YAML", "top level must be a mapping",] - .iter() - .any(|m| message.contains(m)), + [ + "invalid YAML", + "top level must be a mapping", + ] + .iter() + .any(|m| message.contains(m)), "{text:?}: {message}" ); } diff --git a/crates/socket-patch-core/src/policy/tests.rs b/crates/socket-patch-core/src/policy/tests.rs index f64635eba..9a6d247a2 100644 --- a/crates/socket-patch-core/src/policy/tests.rs +++ b/crates/socket-patch-core/src/policy/tests.rs @@ -418,14 +418,8 @@ fn composer_package_filters_match_release_identity_and_preserve_branch_case() { Err(FilterReason::PackageIgnored { .. }) )); assert!(policy.admits_purl("pkg:composer/psr/log@3.0.3").is_ok()); - assert!(package_spec_matches( - "pkg:composer/PSR/Log@3.0.2.0", - "pkg:composer/psr/log@3.0.2" - )); - assert!(!package_spec_matches( - "pkg:composer/psr/log@dev-Feature", - "pkg:composer/psr/log@dev-feature" - )); + assert!(package_spec_matches("pkg:composer/PSR/Log@3.0.2.0", "pkg:composer/psr/log@3.0.2")); + assert!(!package_spec_matches("pkg:composer/psr/log@dev-Feature", "pkg:composer/psr/log@dev-feature")); } #[test] @@ -583,10 +577,7 @@ mod disk { assert!(owner_trusted(1000, 1000, None)); assert!(owner_trusted(0, 1000, None)); assert!(!owner_trusted(1001, 1000, None)); - assert!( - owner_trusted(1001, 1000, Some(1001)), - "sudo's invoking user" - ); + assert!(owner_trusted(1001, 1000, Some(1001)), "sudo's invoking user"); assert!(owner_trusted(1001, 0, None), "root trusts every owner"); } @@ -607,21 +598,13 @@ mod disk { fn this_repos_socket_yml_loads_and_excludes_its_fixtures() { let repo = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); let (policy, warnings) = - SelectionPolicy::load(&DiskPolicyFs::new(&repo), &PolicyOverrides::default()) - .expect("valid"); + SelectionPolicy::load(&DiskPolicyFs::new(&repo), &PolicyOverrides::default()).expect("valid"); assert!(warnings.is_empty(), "{warnings:?}"); assert!(matches!(policy.source(), PolicySource::File { path, .. } if path == "socket.yml")); let lock = strings(&["package-lock.json"]); let err = policy - .admits_root(&root( - "crates/socket-patch-core/tests/fixtures/redirect/npm", - &lock, - true, - )) + .admits_root(&root("crates/socket-patch-core/tests/fixtures/redirect/npm", &lock, true)) .unwrap_err(); - assert_eq!( - err.detail(), - "crates/socket-patch-core/tests/fixtures/** (projectIgnorePaths)" - ); + assert_eq!(err.detail(), "crates/socket-patch-core/tests/fixtures/** (projectIgnorePaths)"); assert!(policy.admits_root(&root("", &lock, true)).is_ok()); } diff --git a/crates/socket-patch-core/src/rollout/stage.rs b/crates/socket-patch-core/src/rollout/stage.rs index 7c24ebadf..9ebd7e7ac 100644 --- a/crates/socket-patch-core/src/rollout/stage.rs +++ b/crates/socket-patch-core/src/rollout/stage.rs @@ -394,11 +394,7 @@ impl Stage { "a patch lookup failed for a package that could get its first patch, so \ no new patches were added this run ({} deferred) and none can take the \ missing package's place; re-run once the API answers", - if deferred == 1 { - "1 package".to_string() - } else { - format!("{deferred} packages") - } + if deferred == 1 { "1 package".to_string() } else { format!("{deferred} packages") } ), )); } @@ -419,9 +415,7 @@ impl Stage { purl: c.purl.clone(), uuid: c.uuid.clone(), reason: ROLLOUT_DEFERRED.to_string(), - detail: Some(format!( - "rank {rank} in the rollout queue; a later scan adds it" - )), + detail: Some(format!("rank {rank} in the rollout queue; a later scan adds it")), }) .collect() } @@ -508,3 +502,4 @@ pub fn rollout_json(configured: &MaxNew, plan: Option<&RolloutPlan>) -> serde_js "deferred": deferred, }) } + diff --git a/crates/socket-patch-core/src/telemetry.rs b/crates/socket-patch-core/src/telemetry.rs index 5f0eccb8d..d49aaa5c6 100644 --- a/crates/socket-patch-core/src/telemetry.rs +++ b/crates/socket-patch-core/src/telemetry.rs @@ -4,7 +4,9 @@ use once_cell::sync::Lazy; use uuid::Uuid; use crate::constants::USER_AGENT; -use crate::utils::env_compat::{is_debug_enabled, is_offline_env, proxy_url_from_env}; +use crate::utils::env_compat::{ + is_debug_enabled, is_offline_env, proxy_url_from_env, +}; use crate::utils::fs::home_dir; use crate::vex::time::unix_to_ymdhms; diff --git a/crates/socket-patch-core/src/update/download.rs b/crates/socket-patch-core/src/update/download.rs index fbee9ebc8..ea7339f63 100644 --- a/crates/socket-patch-core/src/update/download.rs +++ b/crates/socket-patch-core/src/update/download.rs @@ -739,10 +739,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let missing = tmp.path().join("never-existed"); sweep_stale_stages(&missing); - assert!( - !missing.exists(), - "sweep must not create the destination dir" - ); + assert!(!missing.exists(), "sweep must not create the destination dir"); } /// A write failure AFTER a successful open (EFBIG here, standing in @@ -758,7 +755,8 @@ mod tests { #[test] fn stage_write_failure_cleans_up_stage_file() { const CHILD_ENV: &str = "SOCKET_PATCH_CORE_TEST_STAGE_FSIZE_CHILD"; - const TEST_NAME: &str = "update::download::tests::stage_write_failure_cleans_up_stage_file"; + const TEST_NAME: &str = + "update::download::tests::stage_write_failure_cleans_up_stage_file"; if std::env::var_os(CHILD_ENV).is_none() { let exe = std::env::current_exe().expect("test binary path must resolve"); let output = std::process::Command::new(exe) @@ -824,10 +822,7 @@ mod tests { matches!(err, UpdateError::SwapFailed(_)), "expected SwapFailed, got: {err}" ); - assert!( - err.to_string().contains("error writing staged binary"), - "{err}" - ); + assert!(err.to_string().contains("error writing staged binary"), "{err}"); let leftovers: Vec = std::fs::read_dir(tmp.path()) .unwrap() .map(|e| e.unwrap().file_name().to_string_lossy().into_owned()) diff --git a/crates/socket-patch-core/src/update/release.rs b/crates/socket-patch-core/src/update/release.rs index 7c3b04c29..5b2869012 100644 --- a/crates/socket-patch-core/src/update/release.rs +++ b/crates/socket-patch-core/src/update/release.rs @@ -751,11 +751,9 @@ mod tests { .mount(&server) .await; - let client = metadata_client( - &short_timeouts(), - follow_redirect_policy(&default_endpoints()), - ) - .unwrap(); + let client = + metadata_client(&short_timeouts(), follow_redirect_policy(&default_endpoints())) + .unwrap(); let err = client .get(format!("{}/start", server.uri())) .send() @@ -788,11 +786,9 @@ mod tests { .mount(&server) .await; - let client = metadata_client( - &short_timeouts(), - follow_redirect_policy(&default_endpoints()), - ) - .unwrap(); + let client = + metadata_client(&short_timeouts(), follow_redirect_policy(&default_endpoints())) + .unwrap(); let err = client .get(format!("{}/start", server.uri())) .send() @@ -868,10 +864,7 @@ mod tests { .unwrap_err(); assert!(matches!(err, UpdateError::CheckFailed(_)), "{err:?}"); let msg = err.to_string(); - assert!( - msg.contains("expected a redirect to the latest tag"), - "{msg}" - ); + assert!(msg.contains("expected a redirect to the latest tag"), "{msg}"); assert!(msg.contains("API fallback:"), "{msg}"); assert!(msg.contains("returned 500"), "{msg}"); } @@ -952,14 +945,8 @@ mod tests { #[test] fn url_host_keeps_explicit_ports() { - assert_eq!( - url_host("http://127.0.0.1:9/x").as_deref(), - Some("127.0.0.1:9") - ); - assert_eq!( - url_host("https://github.com/a").as_deref(), - Some("github.com") - ); + assert_eq!(url_host("http://127.0.0.1:9/x").as_deref(), Some("127.0.0.1:9")); + assert_eq!(url_host("https://github.com/a").as_deref(), Some("github.com")); assert_eq!(url_host("not a url"), None); } @@ -972,9 +959,7 @@ mod tests { // code stays `check_failed` (stable contract). let server = MockServer::start().await; Mock::given(method("GET")) - .and(path( - "/SocketDev/socket-patch/releases/download/v1.2.3/SHA256SUMS", - )) + .and(path("/SocketDev/socket-patch/releases/download/v1.2.3/SHA256SUMS")) .respond_with(ResponseTemplate::new(404)) .mount(&server) .await; @@ -1007,9 +992,7 @@ mod tests { // silently. let server = MockServer::start().await; Mock::given(method("GET")) - .and(path( - "/SocketDev/socket-patch/releases/download/v1.2.3/SHA256SUMS", - )) + .and(path("/SocketDev/socket-patch/releases/download/v1.2.3/SHA256SUMS")) .respond_with(ResponseTemplate::new(500)) .mount(&server) .await; diff --git a/crates/socket-patch-core/src/utils/group_commit.rs b/crates/socket-patch-core/src/utils/group_commit.rs index 94a2c1118..7b93ada11 100644 --- a/crates/socket-patch-core/src/utils/group_commit.rs +++ b/crates/socket-patch-core/src/utils/group_commit.rs @@ -304,9 +304,9 @@ where // write the lock edits beside the pre-run ledger. Put the caller's value // back before the unwind continues — the same value a caught-and- // continued caller holds. - if let Err(panic) = - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| edit(Arc::make_mut(value)))) - { + if let Err(panic) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + edit(Arc::make_mut(value)) + })) { files.insert(key, captured(value)); drop(files); std::panic::resume_unwind(panic); @@ -1766,10 +1766,7 @@ mod tests { .unwrap(); remove_dir_after_commit(&dir).await; drop(dropped); - assert!( - dir.join("config.toml").exists(), - "an abandoned commit removes nothing" - ); + assert!(dir.join("config.toml").exists(), "an abandoned commit removes nothing"); let group = GroupCommit::begin(root); super::super::fs::remove_file(&dir.join("config.toml")) @@ -1778,10 +1775,7 @@ mod tests { remove_dir_after_commit(&dir).await; assert!(dir.join("config.toml").exists(), "captured, still on disk"); group.commit().await.unwrap(); - assert!( - !dir.exists(), - "the emptied directory is removed after the commit" - ); + assert!(!dir.exists(), "the emptied directory is removed after the commit"); std::fs::create_dir_all(&dir).unwrap(); std::fs::write(dir.join("config.toml"), b"[patch]\n").unwrap(); @@ -1793,10 +1787,7 @@ mod tests { remove_dir_after_commit(&dir).await; group.commit().await.unwrap(); assert!(!dir.join("config.toml").exists()); - assert!( - dir.join("credentials.toml").exists(), - "a non-empty directory is kept" - ); + assert!(dir.join("credentials.toml").exists(), "a non-empty directory is kept"); remove_dir_after_commit(&root.join("gone")).await; std::fs::remove_file(dir.join("credentials.toml")).unwrap(); diff --git a/crates/socket-patch-core/src/utils/line_endings.rs b/crates/socket-patch-core/src/utils/line_endings.rs index c6f257359..889f6ad32 100644 --- a/crates/socket-patch-core/src/utils/line_endings.rs +++ b/crates/socket-patch-core/src/utils/line_endings.rs @@ -119,4 +119,5 @@ mod tests { assert_eq!(majority_terminator("a\r\nb\n"), "\n", "a tie is LF"); assert_eq!(majority_terminator("{}"), "\n", "no break: LF, not os.EOL"); } + } diff --git a/crates/socket-patch-core/src/utils/process.rs b/crates/socket-patch-core/src/utils/process.rs index 733489194..c69552a73 100644 --- a/crates/socket-patch-core/src/utils/process.rs +++ b/crates/socket-patch-core/src/utils/process.rs @@ -89,7 +89,9 @@ pub(crate) fn resolve_app_alias_with( std::env::split_paths(&path) .filter(|dir| dir.is_absolute()) .map(|dir| dir.join(format!("{name}.exe"))) - .find(|candidate| std::fs::symlink_metadata(candidate).is_ok_and(|meta| !meta.is_dir())) + .find(|candidate| { + std::fs::symlink_metadata(candidate).is_ok_and(|meta| !meta.is_dir()) + }) } /// A plain file that cannot be executed (a stray `bun` data file on PATH) @@ -425,11 +427,7 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); let safe = tmp.path().join("bin"); std::fs::create_dir_all(&safe).unwrap(); - let relative = [ - PathBuf::from("."), - PathBuf::from(""), - PathBuf::from("planted"), - ]; + let relative = [PathBuf::from("."), PathBuf::from(""), PathBuf::from("planted")]; let only_relative = std::env::join_paths(&relative).unwrap(); let var = |name: &str| (name == "PATH").then(|| only_relative.clone()); @@ -439,10 +437,7 @@ mod tests { let with_safe = std::env::join_paths(relative.iter().cloned().chain([safe.clone()])).unwrap(); let var = |name: &str| (name == "PATH").then(|| with_safe.clone()); - assert_eq!( - resolve_app_alias_with("yarn", &var), - Some(safe.join("yarn.exe")) - ); + assert_eq!(resolve_app_alias_with("yarn", &var), Some(safe.join("yarn.exe"))); } #[test] diff --git a/crates/socket-patch-core/src/utils/python_script.rs b/crates/socket-patch-core/src/utils/python_script.rs index 72ffdcf0c..df7d84223 100644 --- a/crates/socket-patch-core/src/utils/python_script.rs +++ b/crates/socket-patch-core/src/utils/python_script.rs @@ -686,12 +686,7 @@ mod rendering_tests { "{direct}" ); assert!(uv_line.ends_with('}'), "{direct}"); - assert!( - direct.starts_with( - "[project]\nname = \"p\"\ndependencies = [\"alpha==1.0.0\"]\n\n[tool]\n" - ), - "{direct}" - ); + assert!(direct.starts_with("[project]\nname = \"p\"\ndependencies = [\"alpha==1.0.0\"]\n\n[tool]\n"), "{direct}"); assert_settled(&direct); let transitive = rewrite_project_metadata( diff --git a/crates/socket-patch-core/src/vendor/bun_lockb.rs b/crates/socket-patch-core/src/vendor/bun_lockb.rs index 10173ec37..f349f94b6 100644 --- a/crates/socket-patch-core/src/vendor/bun_lockb.rs +++ b/crates/socket-patch-core/src/vendor/bun_lockb.rs @@ -1872,10 +1872,7 @@ mod tests { lock.set_package(package.id, &repin, &digest()).unwrap(); assert_eq!(lock.bytes().len(), first.len(), "{version}"); assert!( - !lock - .bytes() - .windows(token.len()) - .any(|w| w == token.as_bytes()), + !lock.bytes().windows(token.len()).any(|w| w == token.as_bytes()), "{version}: the superseded URL is gone" ); // A remote tarball keeps the registry record's inactive bytes; a @@ -1918,9 +1915,7 @@ mod tests { .set_package(1, ".socket/vendor/npm/x/minimist-1.2.2.tgz", &digest()) .unwrap(); let at = local.resolution_at(1); - assert!(local.data[at + 16..at + local.resolution_size] - .iter() - .all(|b| *b == 0)); + assert!(local.data[at + 16..at + local.resolution_size].iter().all(|b| *b == 0)); } #[test] diff --git a/crates/socket-patch-core/src/vendor/cargo_lock.rs b/crates/socket-patch-core/src/vendor/cargo_lock.rs index 73bdf8275..7e50bccaf 100644 --- a/crates/socket-patch-core/src/vendor/cargo_lock.rs +++ b/crates/socket-patch-core/src/vendor/cargo_lock.rs @@ -64,9 +64,11 @@ use std::sync::Arc; use toml_edit::{DocumentMut, Item, Table}; use super::cargo_tag; +use crate::formats::cargo::{ + locked_packages, metadata_checksum_key, parse_ref, LockedPackage, +}; use super::parse_memo::ParseMemo; use super::state::CargoLockOriginal; -use crate::formats::cargo::{locked_packages, metadata_checksum_key, parse_ref, LockedPackage}; use crate::utils::fs::{atomic_write_bytes_preserving_mode, read_regular_to_string}; /// Why a lock edit could not be performed. diff --git a/crates/socket-patch-core/src/vendor/gem.rs b/crates/socket-patch-core/src/vendor/gem.rs index 77613e080..a9e7d65ff 100644 --- a/crates/socket-patch-core/src/vendor/gem.rs +++ b/crates/socket-patch-core/src/vendor/gem.rs @@ -1636,14 +1636,12 @@ fn rest_blocks_edit(rest: &str) -> Option { } // A `**opts` splat or hash literal is kept after `path:` (#847): a // source hidden in it makes bundler refuse the Gemfile loudly. - gemfile::source_option(rest) - .filter(|opt| !opt.dynamic) - .map(|opt| { - format!( - "the declaration already carries `{}` (revert any previous vendoring first)", - opt.spelling - ) - }) + gemfile::source_option(rest).filter(|opt| !opt.dynamic).map(|opt| { + format!( + "the declaration already carries `{}` (revert any previous vendoring first)", + opt.spelling + ) + }) } /// The quoted `path:` option value on a gem line's argument tail (only the diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/view.rs b/crates/socket-patch-core/src/vendor/lock_inventory/view.rs index 29a446efc..acd48d721 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/view.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/view.rs @@ -15,10 +15,10 @@ use std::sync::Arc; use crate::constants::npm_family::{ BUN_LOCK, BUN_LOCKB, NPM_LOCKS, PNPM_LOCK, PNP_MARKERS, VLT_LOCK, }; -use crate::formats::pnpm::{sniff_lock_grammar, PnpmLockGrammar}; -use crate::formats::yarn::{sniff_grammar, YarnLockGrammar, UNIDENTIFIED_DETAIL}; use crate::utils::fs::{read_regular_to_bytes, read_regular_to_string}; use crate::vendor::npm_flavor::NpmLockFlavor; +use crate::formats::pnpm::{sniff_lock_grammar, PnpmLockGrammar}; +use crate::formats::yarn::{sniff_grammar, YarnLockGrammar, UNIDENTIFIED_DETAIL}; use crate::vendor::VendorWarning; /// One in-memory file. diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs b/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs index c3c21f8e9..9ed45e49d 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs @@ -7,12 +7,12 @@ use toml_edit::{DocumentMut, Item}; use crate::constants::npm_family::{BUN_LOCK, BUN_LOCKB, NPM_LOCKS, PNPM_LOCK}; use crate::formats::pnpm::PnpmLock; -use crate::formats::yarn::is_berry_lock; use crate::utils::digest::is_sri_pin; use crate::utils::fs::{read_regular_to_bytes, read_regular_to_string}; use crate::utils::python_lock::{ lock_artifact, lock_package_collection, package_artifacts, uv_source_location, }; +use crate::formats::yarn::is_berry_lock; use crate::vendor::bun_lock_text::{decode_json_string, split_name_spec}; use crate::vendor::bun_lockb::BunLockb; use crate::vendor::yarn_berry_lock::berry_field; diff --git a/crates/socket-patch-core/src/vendor/prestage.rs b/crates/socket-patch-core/src/vendor/prestage.rs index 78f5d4c98..fcc149cc1 100644 --- a/crates/socket-patch-core/src/vendor/prestage.rs +++ b/crates/socket-patch-core/src/vendor/prestage.rs @@ -490,10 +490,7 @@ mod sweep_tests { for dir in &kept { assert!(v.join(dir).exists(), "{dir} kept"); } - assert!( - !v.join("gem").exists(), - "the levels only the tree kept alive are pruned" - ); + assert!(!v.join("gem").exists(), "the levels only the tree kept alive are pruned"); assert!(!v.join(format!("composer/{u}/psr/log@3.0.2")).exists()); assert!(v.join("state.json").exists()); assert_eq!(sweep_stale(root).await, 0, "idempotent"); diff --git a/crates/socket-patch-core/src/vendor/toml_surgery.rs b/crates/socket-patch-core/src/vendor/toml_surgery.rs index 4d151f613..0b1777008 100644 --- a/crates/socket-patch-core/src/vendor/toml_surgery.rs +++ b/crates/socket-patch-core/src/vendor/toml_surgery.rs @@ -519,8 +519,7 @@ mod tests { // CRLF, and a hand edit can leave a mixed-ending file, so the // removal helpers must never normalize: every byte outside the // removed segment survives verbatim. - let wired = - "[project]\r\nname = \"x\"\r\n\n[tool.uv.sources]\nfoo = { path = \"w.whl\" }\n"; + let wired = "[project]\r\nname = \"x\"\r\n\n[tool.uv.sources]\nfoo = { path = \"w.whl\" }\n"; let after = remove_exact_line(wired, "foo = { path = \"w.whl\" }").unwrap(); assert_eq!(after, "[project]\r\nname = \"x\"\r\n\n[tool.uv.sources]\n"); assert_eq!( diff --git a/crates/socket-patch-core/src/vex/discover/cargo.rs b/crates/socket-patch-core/src/vex/discover/cargo.rs index 4056ea30b..86aab22de 100644 --- a/crates/socket-patch-core/src/vex/discover/cargo.rs +++ b/crates/socket-patch-core/src/vex/discover/cargo.rs @@ -738,22 +738,23 @@ async fn vendored_from_patches( } let copy_tagged = matches!(tag, CopyTag::Tagged(_) | CopyTag::Unreadable); if let Lock::Parsed(lock) = lock { - let why = match lock.vendored_in_use(name, version, &vref.uuid, copy_tagged) { - CopyClaim::Consumed => None, - CopyClaim::OtherTag(other) => Some(format!( - "{CARGO_LOCK} builds the copy tagged for patch {other} ({name} {})", - cargo_tag::tag_version(version, other) - )), - CopyClaim::UntaggedOverride => Some(format!( - "{CARGO_LOCK} builds an untagged {name} {version}, not the copy (which \ + let why = + match lock.vendored_in_use(name, version, &vref.uuid, copy_tagged) { + CopyClaim::Consumed => None, + CopyClaim::OtherTag(other) => Some(format!( + "{CARGO_LOCK} builds the copy tagged for patch {other} ({name} {})", + cargo_tag::tag_version(version, other) + )), + CopyClaim::UntaggedOverride => Some(format!( + "{CARGO_LOCK} builds an untagged {name} {version}, not the copy (which \ cargo would lock as {}): another [patch] or path dependency overrides it", - cargo_tag::tag_version(version, &vref.uuid) - )), - CopyClaim::NotConsumed => Some(format!( - "{CARGO_LOCK} does not build {name}@{version} from it (an unused patch, \ + cargo_tag::tag_version(version, &vref.uuid) + )), + CopyClaim::NotConsumed => Some(format!( + "{CARGO_LOCK} does not build {name}@{version} from it (an unused patch, \ or the lock resolves it from a registry)" - )), - }; + )), + }; if let Some(why) = why { out.diag( DIAG_REF_INVALID, diff --git a/crates/socket-patch-core/src/vex/discover/maven.rs b/crates/socket-patch-core/src/vex/discover/maven.rs index c880b5afa..c73e1f978 100644 --- a/crates/socket-patch-core/src/vex/discover/maven.rs +++ b/crates/socket-patch-core/src/vex/discover/maven.rs @@ -88,15 +88,15 @@ use super::{ Discovery, PatchedRef, WiringMode, DIAG_LOCKFILE_UNPARSEABLE, DIAG_REF_INVALID, DIAG_REF_UNATTRIBUTABLE, }; -use crate::formats::maven::{ - is_maven_coordinate, is_maven_version_text, parse_pom, split_socket_version, Pom, PomDep, - PomRepo, -}; use crate::patch::redirect::{ local_repo_artifact_path, MVN_CHECKSUMS, MVN_CONFIG, TRUSTED_CHECKSUMS_ON, }; use crate::utils::digest::sha256_hex; use crate::vendor::lock_inventory::LockIntegrity; +use crate::formats::maven::{ + is_maven_coordinate, is_maven_version_text, parse_pom, split_socket_version, Pom, PomDep, + PomRepo, +}; use crate::vendor::maven_repo::{sha1_sidecar_matches, VENDOR_REPO_URL_PREFIX}; use crate::vendor::path::{sweep_vendor_dirs, VENDOR_DIR}; diff --git a/crates/socket-patch-core/src/vex/discover/nuget.rs b/crates/socket-patch-core/src/vex/discover/nuget.rs index 3f608233f..d7f3cd95d 100644 --- a/crates/socket-patch-core/src/vex/discover/nuget.rs +++ b/crates/socket-patch-core/src/vex/discover/nuget.rs @@ -73,8 +73,8 @@ use super::{ Discovery, PatchedRef, UnlockedPin, WiringMode, DIAG_LOCKFILE_UNPARSEABLE, DIAG_REF_INVALID, DIAG_REF_UNATTRIBUTABLE, }; -use crate::formats::nuget::{parse_config, NugetConfig}; use crate::vendor::lock_inventory::LockIntegrity; +use crate::formats::nuget::{parse_config, NugetConfig}; use crate::vendor::nuget_config::{same_file, CONFIG_NAMES}; use crate::vendor::nuget_feed::{is_plain_nuget_token, nuget_lock_entries, nupkg_leaf}; use crate::vendor::path::VENDOR_DIR; diff --git a/crates/socket-patch-core/tests/covgap_api_blob_fetcher.rs b/crates/socket-patch-core/tests/covgap_api_blob_fetcher.rs index ffe7b5943..041c323ad 100644 --- a/crates/socket-patch-core/tests/covgap_api_blob_fetcher.rs +++ b/crates/socket-patch-core/tests/covgap_api_blob_fetcher.rs @@ -569,8 +569,5 @@ async fn fetch_missing_blobs_mixed_outcomes_aggregate_and_format() { // End-to-end formatter exercise with a genuinely mixed result. let rendered = format_fetch_result(&result); assert!(rendered.contains("Downloaded 1 blob\n"), "{rendered}"); - assert!( - rendered.contains("Failed to download 2 blobs"), - "{rendered}" - ); + assert!(rendered.contains("Failed to download 2 blobs"), "{rendered}"); } diff --git a/crates/socket-patch-core/tests/covgap_crawlers_composer_crawler.rs b/crates/socket-patch-core/tests/covgap_crawlers_composer_crawler.rs index 997175812..3c4c872f5 100644 --- a/crates/socket-patch-core/tests/covgap_crawlers_composer_crawler.rs +++ b/crates/socket-patch-core/tests/covgap_crawlers_composer_crawler.rs @@ -99,11 +99,7 @@ async fn get_vendor_paths_global_nonexistent_composer_home_falls_back() { fn write_composer_shim(dir: &Path, echo_path: &Path) { use std::os::unix::fs::PermissionsExt; let shim = dir.join("composer"); - std::fs::write( - &shim, - format!("#!/bin/sh\necho '{}'\n", echo_path.display()), - ) - .unwrap(); + std::fs::write(&shim, format!("#!/bin/sh\necho '{}'\n", echo_path.display())).unwrap(); std::fs::set_permissions(&shim, std::fs::Permissions::from_mode(0o755)).unwrap(); } diff --git a/crates/socket-patch-core/tests/hosted_inventory.rs b/crates/socket-patch-core/tests/hosted_inventory.rs index db1ecd6ae..1bb3772d2 100644 --- a/crates/socket-patch-core/tests/hosted_inventory.rs +++ b/crates/socket-patch-core/tests/hosted_inventory.rs @@ -51,15 +51,9 @@ async fn contradicted_hosted_lock_is_contested_not_absent() { assert!(!inv.is_empty(), "contested wiring is hosted state: {inv:?}"); let refusal = inv.contested_refusal().expect("a refusal"); assert!(refusal.contains("npm-shrinkwrap.json"), "{refusal}"); - assert!( - refusal.contains("git checkout -- npm-shrinkwrap.json"), - "{refusal}" - ); + assert!(refusal.contains("git checkout -- npm-shrinkwrap.json"), "{refusal}"); assert!(refusal.contains("patched_ref_unattributable"), "{refusal}"); - assert!( - !refusal.contains(GRANT), - "the grant token is not a patch: {refusal}" - ); + assert!(!refusal.contains(GRANT), "the grant token is not a patch: {refusal}"); } #[tokio::test] diff --git a/crates/socket-patch-core/tests/poetry_hosted.rs b/crates/socket-patch-core/tests/poetry_hosted.rs index 2d2e17d5d..bd3ca3c83 100644 --- a/crates/socket-patch-core/tests/poetry_hosted.rs +++ b/crates/socket-patch-core/tests/poetry_hosted.rs @@ -1,4 +1,6 @@ -use socket_patch_core::patch::redirect::{rewrite_registry_redirect, DepOverride, Integrity}; +use socket_patch_core::patch::redirect::{ + rewrite_registry_redirect, DepOverride, Integrity, +}; use socket_patch_core::utils::poetry_lock::rewrite_poetry_lock; use std::collections::BTreeMap; @@ -61,11 +63,7 @@ fn native_lock_generations_redirect_idempotently() { let codes: Vec<&str> = result.warnings.iter().map(|w| w.code.as_str()).collect(); assert_eq!( codes, - if pre_1_4 { - vec!["redirect_poetry_stale_install_risk"] - } else { - vec![] - }, + if pre_1_4 { vec!["redirect_poetry_stale_install_risk"] } else { vec![] }, "{version}: {:?}", result.warnings ); @@ -94,24 +92,12 @@ fn hosted_shapes_match_each_lock_generations_installer() { ) .files["poetry.lock"] .clone(); - assert!( - lock10.contains(&format!("url = \"{URL}#sha256={sha}&\"")), - "{lock10}" - ); + assert!(lock10.contains(&format!("url = \"{URL}#sha256={sha}&\"")), "{lock10}"); assert!(lock10.contains("reference = \"\""), "{lock10}"); - assert!( - lock10.contains(&format!( - "urllib3 = [{{ file = \"{WHEEL}\", hash = \"sha256:{sha}\" }}]" - )), - "{lock10}" - ); + assert!(lock10.contains(&format!("urllib3 = [{{ file = \"{WHEEL}\", hash = \"sha256:{sha}\" }}]")), "{lock10}"); // Poetry >= 1.2 consuming this 1.0 lock verifies the package `files` // entry, so it is written too (1.0 ignores the extra key). - assert_eq!( - lock10.matches(&format!("sha256:{sha}")).count(), - 2, - "{lock10}" - ); + assert_eq!(lock10.matches(&format!("sha256:{sha}")).count(), 2, "{lock10}"); let doc: toml_edit::DocumentMut = lock10.parse().unwrap(); assert!(doc["package"][0]["files"].is_array(), "{lock10}"); @@ -138,11 +124,7 @@ fn hosted_shapes_match_each_lock_generations_installer() { &BTreeMap::from([("poetry.lock".to_string(), lock10_populated)]), &[patch()], ); - assert!( - rerun.files.is_empty() && rerun.warnings.is_empty(), - "{:?}", - rerun.warnings - ); + assert!(rerun.files.is_empty() && rerun.warnings.is_empty(), "{:?}", rerun.warnings); let lock11 = rewrite_registry_redirect( &BTreeMap::from([("poetry.lock".to_string(), original("1.2.2"))]), @@ -150,15 +132,8 @@ fn hosted_shapes_match_each_lock_generations_installer() { ) .files["poetry.lock"] .clone(); - assert_eq!( - lock11.matches(&format!("sha256:{sha}")).count(), - 2, - "package files + metadata.files:\n{lock11}" - ); - assert!( - lock11.contains(&format!("url = \"{URL}\"")), - "no fragment on 1.1" - ); + assert_eq!(lock11.matches(&format!("sha256:{sha}")).count(), 2, "package files + metadata.files:\n{lock11}"); + assert!(lock11.contains(&format!("url = \"{URL}\"")), "no fragment on 1.1"); assert!(!lock11.contains("reference"), "{lock11}"); let doc: toml_edit::DocumentMut = lock11.parse().unwrap(); assert!(doc["package"][0]["files"].is_array()); @@ -170,19 +145,11 @@ fn hosted_shapes_match_each_lock_generations_installer() { ) .files["poetry.lock"] .clone(); - assert_eq!( - lock21.matches(&format!("sha256:{sha}")).count(), - 1, - "{lock21}" - ); + assert_eq!(lock21.matches(&format!("sha256:{sha}")).count(), 1, "{lock21}"); assert!(!lock21.contains("reference")); let pristine: toml_edit::DocumentMut = original("2.4.3").parse().unwrap(); let doc: toml_edit::DocumentMut = lock21.parse().unwrap(); - assert_eq!( - doc["metadata"].to_string(), - pristine["metadata"].to_string(), - "[metadata] untouched on 2.x" - ); + assert_eq!(doc["metadata"].to_string(), pristine["metadata"].to_string(), "[metadata] untouched on 2.x"); } #[test] @@ -281,21 +248,14 @@ fn absent_entries_warn_once_and_missing_sha256_is_gated_once_per_dep() { let codes: Vec<&str> = result.warnings.iter().map(|w| w.code.as_str()).collect(); assert_eq!( codes, - vec![ - "redirect_poetry_entry_not_found", - "redirect_poetry_entry_not_found" - ] + vec!["redirect_poetry_entry_not_found", "redirect_poetry_entry_not_found"] ); let mut missing_hash = patch(); missing_hash.integrity.sha256 = None; let result = rewrite_registry_redirect(&files, &[missing_hash]); assert!(result.files.is_empty()); let codes: Vec<&str> = result.warnings.iter().map(|w| w.code.as_str()).collect(); - assert_eq!( - codes, - vec!["redirect_poetry_missing_sha256"], - "gated once, not once per lock" - ); + assert_eq!(codes, vec!["redirect_poetry_missing_sha256"], "gated once, not once per lock"); } /// A future Poetry that bumps the lock minor (2.2) is rewritten like 2.1 in @@ -318,20 +278,11 @@ fn rotated_grant_token_supersedes_the_prior_hosted_url() { let first = rewrite_registry_redirect(&files, &[patch()]); let mut rotated = patch(); rotated.token = "00000000-0000-4000-8000-000000000000".into(); - rotated.artifact_url = URL.replace( - "7e52b8b6-53f2-4dc8-860a-1ae7ebd8be0e", - "00000000-0000-4000-8000-000000000000", - ); + rotated.artifact_url = URL.replace("7e52b8b6-53f2-4dc8-860a-1ae7ebd8be0e", "00000000-0000-4000-8000-000000000000"); let second = rewrite_registry_redirect(&first.files, &[rotated.clone()]); assert!(second.warnings.is_empty(), "{:?}", second.warnings); let lock = &second.files["poetry.lock"]; assert!(lock.contains(&rotated.artifact_url) && !lock.contains(URL)); assert_eq!(second.edits.len(), 1); - assert!(second.edits[0] - .original - .as_ref() - .unwrap() - .as_str() - .unwrap() - .contains(URL)); + assert!(second.edits[0].original.as_ref().unwrap().as_str().unwrap().contains(URL)); } diff --git a/crates/socket-patch-core/tests/telemetry_helpers_e2e.rs b/crates/socket-patch-core/tests/telemetry_helpers_e2e.rs index abde352bb..33c34297c 100644 --- a/crates/socket-patch-core/tests/telemetry_helpers_e2e.rs +++ b/crates/socket-patch-core/tests/telemetry_helpers_e2e.rs @@ -18,7 +18,11 @@ use socket_patch_core::telemetry::{is_telemetry_disabled, sanitize_error_message /// Every environment variable that can independently disable telemetry. /// Scrubbing the full set is what makes the per-var causation asserts honest. -const DISABLE_VARS: &[&str] = &["SOCKET_TELEMETRY_DISABLED", "VITEST", "SOCKET_OFFLINE"]; +const DISABLE_VARS: &[&str] = &[ + "SOCKET_TELEMETRY_DISABLED", + "VITEST", + "SOCKET_OFFLINE", +]; /// Run `f` with all telemetry-disabling vars removed, restoring the prior /// values afterward even if `f` panics (so one failing assert can't poison diff --git a/crates/socket-patch-core/tests/upstream_restore_golden.rs b/crates/socket-patch-core/tests/upstream_restore_golden.rs index 33ec523f2..863f8dc99 100644 --- a/crates/socket-patch-core/tests/upstream_restore_golden.rs +++ b/crates/socket-patch-core/tests/upstream_restore_golden.rs @@ -13,12 +13,10 @@ use std::fs; use std::path::{Path, PathBuf}; use serial_test::serial; +use socket_patch_core::patch::redirect::{rewrite_registry_redirect_with_pipenv_version, DepOverride}; use socket_patch_core::patch::redirect::upstream::{ restore_upstream, HostedPin, PinStatus, RestoreOptions, }; -use socket_patch_core::patch::redirect::{ - rewrite_registry_redirect_with_pipenv_version, DepOverride, -}; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -31,10 +29,7 @@ fn walk(dir: &Path) -> BTreeMap { if !dir.is_dir() { return out; } - for entry in walkdir::WalkDir::new(dir) - .into_iter() - .filter_map(Result::ok) - { + for entry in walkdir::WalkDir::new(dir).into_iter().filter_map(Result::ok) { if entry.file_type().is_file() { let rel = entry .path() @@ -50,27 +45,16 @@ fn walk(dir: &Path) -> BTreeMap { /// Tokens of `pattern` that `input` holds and `expected` does not: the /// upstream values the hosted rewrite replaced. -fn vanished( - input: &BTreeMap, - expected: &BTreeMap, - re: &str, -) -> Vec { +fn vanished(input: &BTreeMap, expected: &BTreeMap, re: &str) -> Vec { let re = regex::Regex::new(re).unwrap(); let all = |files: &BTreeMap| -> BTreeSet { files .values() - .flat_map(|t| { - re.captures_iter(t) - .map(|c| c[1].to_string()) - .collect::>() - }) + .flat_map(|t| re.captures_iter(t).map(|c| c[1].to_string()).collect::>()) .collect() }; let after = all(expected); - let mut out: Vec = all(input) - .into_iter() - .filter(|t| !after.contains(t)) - .collect(); + let mut out: Vec = all(input).into_iter().filter(|t| !after.contains(t)).collect(); out.sort(); out } @@ -96,9 +80,10 @@ fn load(flavor: &str) -> Vec { // `expected/` holds only the files the rewrite changed. let mut expected = input.clone(); expected.extend(walk(&dir.join("expected"))); - let overrides = - serde_json::from_str(&fs::read_to_string(dir.join("overrides.json")).unwrap()) - .unwrap(); + let overrides = serde_json::from_str( + &fs::read_to_string(dir.join("overrides.json")).unwrap(), + ) + .unwrap(); Case { dir, input, @@ -147,23 +132,10 @@ async fn run_case_with( (walk(tmp.path()), statuses) } -fn assert_round_trip( - case: &Case, - after: &BTreeMap, - statuses: &[(String, PinStatus)], -) { - assert!( - !statuses.is_empty(), - "{}: discovery found no hosted pin", - case.dir.display() - ); +fn assert_round_trip(case: &Case, after: &BTreeMap, statuses: &[(String, PinStatus)]) { + assert!(!statuses.is_empty(), "{}: discovery found no hosted pin", case.dir.display()); for (purl, status) in statuses { - assert_eq!( - *status, - PinStatus::Restored, - "{}: {purl}", - case.dir.display() - ); + assert_eq!(*status, PinStatus::Restored, "{}: {purl}", case.dir.display()); } for (rel, want) in &case.input { assert_eq!( @@ -173,15 +145,8 @@ fn assert_round_trip( case.dir.display() ); } - let extra: Vec<&String> = after - .keys() - .filter(|k| !case.input.contains_key(*k)) - .collect(); - assert!( - extra.is_empty(), - "{}: left behind {extra:?}", - case.dir.display() - ); + let extra: Vec<&String> = after.keys().filter(|k| !case.input.contains_key(*k)).collect(); + assert!(extra.is_empty(), "{}: left behind {extra:?}", case.dir.display()); } /// Sets env vars for the guard's lifetime (tests using it are `#[serial]`). @@ -242,9 +207,10 @@ async fn npm_mock(case: &Case) -> MockServer { } Mock::given(method("GET")) .and(path(format!("/{}/{version}", name.replace('/', "%2f")))) - .respond_with(ResponseTemplate::new(200).set_body_json( - serde_json::json!({ "name": name, "version": version, "dist": dist }), - )) + .respond_with( + ResponseTemplate::new(200) + .set_body_json(serde_json::json!({ "name": name, "version": version, "dist": dist })), + ) .mount(&server) .await; } @@ -483,12 +449,7 @@ fn assert_refused( } other => panic!("{}: expected a refusal, got {other:?}", case.dir.display()), } - assert_eq!( - after, - &case.expected, - "{}: a refused pin must change nothing", - case.dir.display() - ); + assert_eq!(after, &case.expected, "{}: a refused pin must change nothing", case.dir.display()); } fn offline() -> RestoreOptions { @@ -580,8 +541,7 @@ fn transitive_lock() -> String { #[serial] async fn gem_edge_shapes_round_trip() { let gemfile = "source \"https://rubygems.org\"\n\ngem \"puma\"\n\ngroup :test do\n gem \"rails\", \"7.0.0\", require: false\nend\n"; - let crlf_gemfile = - "source \"https://rubygems.org\"\r\n\r\ngem \"rails\", \"7.0.0\"\r\ngem \"puma\"\r\n"; + let crlf_gemfile = "source \"https://rubygems.org\"\r\n\r\ngem \"rails\", \"7.0.0\"\r\ngem \"puma\"\r\n"; let two_sources_gemfile = "source \"https://rubygems.org\"\n\ngem \"rails\", \"7.0.0\"\nsource \"https://gems.example.com\" do\n gem \"private-gem\"\nend\n"; let two_sources_lock = "GEM\n remote: https://gems.example.com/\n specs:\n private-gem (1.0.0)\n\nGEM\n remote: https://rubygems.org/\n specs:\n rails (7.0.0)\n\nPLATFORMS\n ruby\n\nDEPENDENCIES\n private-gem!\n rails (= 7.0.0)\n\nCHECKSUMS\n private-gem (1.0.0) sha256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n rails (7.0.0) sha256=2222222222222222222222222222222222222222222222222222222222222222\n\nBUNDLED WITH\n 2.6.2\n"; // Provably transitive: the rewriter appended after a trailing blank @@ -609,18 +569,12 @@ async fn gem_edge_shapes_round_trip() { ), synthetic( "multiple-gem-sections", - &[ - ("Gemfile", two_sources_gemfile), - ("Gemfile.lock", two_sources_lock), - ], + &[("Gemfile", two_sources_gemfile), ("Gemfile.lock", two_sources_lock)], gem_override("rails", "7.0.0"), ), synthetic( "transitive-appended", - &[ - ("Gemfile", transitive_gemfile), - ("Gemfile.lock", &transitive), - ], + &[("Gemfile", transitive_gemfile), ("Gemfile.lock", &transitive)], gem_override("zeitwerk", "2.6.0"), ), ]; @@ -679,10 +633,7 @@ async fn gem_pre_checksums_states() { .replace(" rails (~> 7.0)\n", " rails (= 7.0.0)!\n"); mixed.expected.insert("Gemfile.lock".into(), converged); let (after, statuses) = run_case_with(&mixed, None, &offline()).await; - assert_eq!( - statuses, - vec![("pkg:gem/rails@7.0.0".to_string(), PinStatus::Restored)] - ); + assert_eq!(statuses, vec![("pkg:gem/rails@7.0.0".to_string(), PinStatus::Restored)]); assert_eq!(after["Gemfile.lock"], restored_lock); assert_eq!(after["Gemfile"], restored_gemfile); @@ -695,10 +646,7 @@ async fn gem_pre_checksums_states() { .replace(" rails (~> 7.0)\n", " rails (= 7.0.0)!\n"); mixed.expected.insert("Gemfile.lock".into(), merged); let (after, statuses) = run_case_with(&mixed, None, &offline()).await; - assert_eq!( - statuses, - vec![("pkg:gem/rails@7.0.0".to_string(), PinStatus::Restored)] - ); + assert_eq!(statuses, vec![("pkg:gem/rails@7.0.0".to_string(), PinStatus::Restored)]); assert_eq!(after["Gemfile.lock"], restored_lock); assert_eq!(after["Gemfile"], restored_gemfile); } @@ -728,10 +676,7 @@ async fn gem_transitive_append_round_trips_unless_unprovable() { case.expected.insert("Gemfile".into(), legacy); let (after, statuses) = gem_run(&case).await; assert_eq!(statuses[0].1, PinStatus::Restored); - assert_eq!( - after["Gemfile"], - format!("{gemfile}gem \"zeitwerk\", \"2.6.0\"\n") - ); + assert_eq!(after["Gemfile"], format!("{gemfile}gem \"zeitwerk\", \"2.6.0\"\n")); assert_eq!( after["Gemfile.lock"], lock.replace(" puma\n", " puma\n zeitwerk (= 2.6.0)\n") @@ -760,19 +705,10 @@ async fn gem_refusals_leave_everything_hosted() { // The upstream section is another registry's. let mut foreign = case.clone_with("foreign-upstream"); foreign.edit_both("Gemfile.lock", |t| { - t.replace( - "remote: https://rubygems.org/", - "remote: https://gems.example.com/", - ) + t.replace("remote: https://rubygems.org/", "remote: https://gems.example.com/") }); let (after, statuses) = gem_run(&foreign).await; - assert_refused( - &foreign, - &after, - &statuses, - "Gemfile.lock", - "not rubygems.org", - ); + assert_refused(&foreign, &after, &statuses, "Gemfile.lock", "not rubygems.org"); // Two upstream sections, neither singled out. let mut ambiguous = case.clone_with("ambiguous-upstream"); ambiguous.edit_both("Gemfile.lock", |t| { @@ -781,38 +717,18 @@ async fn gem_refusals_leave_everything_hosted() { "GEM\n remote: https://gems.example.com/\n specs:\n other (1.0.0)\n\nPLATFORMS", ) }); - ambiguous.edit_both("Gemfile", |t| { - t.replace("source \"https://rubygems.org\"\n", "") - }); - ambiguous.edit_both("Gemfile.lock", |t| { - t.replace( - "remote: https://rubygems.org/", - "remote: https://mirror.example.com/", - ) - }); + ambiguous.edit_both("Gemfile", |t| t.replace("source \"https://rubygems.org\"\n", "")); + ambiguous.edit_both("Gemfile.lock", |t| t.replace("remote: https://rubygems.org/", "remote: https://mirror.example.com/")); let (after, statuses) = gem_run(&ambiguous).await; - assert_refused( - &ambiguous, - &after, - &statuses, - "Gemfile.lock", - "upstream GEM sections", - ); + assert_refused(&ambiguous, &after, &statuses, "Gemfile.lock", "upstream GEM sections"); // The Gemfile block was hand-edited. let mut edited = case.clone_with("edited-block"); edited.expected.insert( "Gemfile".into(), - edited.expected["Gemfile"] - .replace(" gem \"rails\", \"7.0.0\"", " gem \"rails\", \"~> 7.0\""), + edited.expected["Gemfile"].replace(" gem \"rails\", \"7.0.0\"", " gem \"rails\", \"~> 7.0\""), ); let (after, statuses) = gem_run(&edited).await; - assert_refused( - &edited, - &after, - &statuses, - "Gemfile.lock", - "shape other than the source block", - ); + assert_refused(&edited, &after, &statuses, "Gemfile.lock", "shape other than the source block"); } // ── composer ──────────────────────────────────────────────────────────────── @@ -981,11 +897,7 @@ async fn composer_edge_shapes_round_trip() { &[("composer.lock", &escaped)], composer_override("acme/tool", "dev-main"), ), - synthetic( - "crlf", - &[("composer.lock", &crlf)], - composer_override("psr/log", "1.1.4"), - ), + synthetic("crlf", &[("composer.lock", &crlf)], composer_override("psr/log", "1.1.4")), ]; for case in &cases { let (after, statuses) = composer_run(case, |_| {}).await; @@ -1004,42 +916,20 @@ async fn composer_refusals_leave_everything_hosted() { // Packagist now serves another commit for the version. let (after, statuses) = composer_run(&case, |d| d["dist"]["reference"] = "feedface".into()).await; - assert_refused( - &case, - &after, - &statuses, - "composer.lock", - "packagist now serves", - ); + assert_refused(&case, &after, &statuses, "composer.lock", "packagist now serves"); // Packagist does not list the version. let (after, statuses) = composer_run(&case, |d| d["version"] = "0.0.1".into()).await; - assert_refused( - &case, - &after, - &statuses, - "composer.lock", - "does not list version 1.1.4", - ); + assert_refused(&case, &after, &statuses, "composer.lock", "does not list version 1.1.4"); // Offline. let (after, statuses) = run_case_with(&case, None, &offline()).await; assert_refused(&case, &after, &statuses, "composer.lock", "offline"); // Locked from another repository. let mut foreign = case.clone_with("foreign"); foreign.edit_both("composer.lock", |t| { - t.replacen( - "https://packagist.org/downloads/", - "https://repo.example.com/downloads/", - 1, - ) + t.replacen("https://packagist.org/downloads/", "https://repo.example.com/downloads/", 1) }); let (after, statuses) = composer_run(&foreign, |_| {}).await; - assert_refused( - &foreign, - &after, - &statuses, - "composer.lock", - "not packagist", - ); + assert_refused(&foreign, &after, &statuses, "composer.lock", "not packagist"); // No notification-url, and composer.json names custom repositories. let mut custom = case.clone_with("custom-repos"); custom.edit_both("composer.lock", |t| { @@ -1056,13 +946,7 @@ async fn composer_refusals_leave_everything_hosted() { ); } let (after, statuses) = composer_run(&custom, |_| {}).await; - assert_refused( - &custom, - &after, - &statuses, - "composer.lock", - "custom repositories", - ); + assert_refused(&custom, &after, &statuses, "composer.lock", "custom repositories"); } // ── PyPI ───────────────────────────────────────────────────────────────────── @@ -1100,11 +984,7 @@ fn urllib3_dep() -> DepOverride { /// PyPI's blake2b-bucketed file URLs, with real urllib3 1.26.18's buckets: the /// sdist sorts before the wheel by URL, the reverse of filename order. fn pypi_file_url(filename: &str) -> String { - let bucket = if filename.ends_with(".tar.gz") { - "0c/39" - } else { - "b0/53" - }; + let bucket = if filename.ends_with(".tar.gz") { "0c/39" } else { "b0/53" }; format!("https://files.pythonhosted.org/packages/{bucket}/{filename}") } @@ -1117,18 +997,8 @@ fn urllib3_release() -> Release<'static> { "urllib3", "1.26.18", vec![ - ( - URLLIB3_WHEEL, - URLLIB3_WHEEL_SHA, - 143835, - "2023-10-17T17:46:21.184066Z", - ), - ( - URLLIB3_SDIST, - URLLIB3_SDIST_SHA, - 305687, - "2023-10-17T17:46:24.000000Z", - ), + (URLLIB3_WHEEL, URLLIB3_WHEEL_SHA, 143835, "2023-10-17T17:46:21.184066Z"), + (URLLIB3_SDIST, URLLIB3_SDIST_SHA, 305687, "2023-10-17T17:46:24.000000Z"), ], ) } @@ -1163,10 +1033,7 @@ async fn pypi_mock(releases: &[Release<'_>]) -> (MockServer, EnvGuard) { } fn tree(files: &[(&str, String)]) -> BTreeMap { - files - .iter() - .map(|(k, v)| (k.to_string(), v.clone())) - .collect() + files.iter().map(|(k, v)| (k.to_string(), v.clone())).collect() } /// `input` as the real hosted rewriter leaves it. @@ -1213,24 +1080,14 @@ async fn assert_pypi_round_trip( pipenv: Option, ) { let rewritten = hosted(input, deps, pipenv); - assert_ne!( - &rewritten, input, - "{label}: the hosted rewrite changed nothing" - ); + assert_ne!(&rewritten, input, "{label}: the hosted rewrite changed nothing"); let (after, statuses) = restore_tree(&rewritten, &RestoreOptions::default()).await; - assert!( - !statuses.is_empty(), - "{label}: discovery found no hosted pin" - ); + assert!(!statuses.is_empty(), "{label}: discovery found no hosted pin"); for (purl, status) in &statuses { assert_eq!(*status, PinStatus::Restored, "{label}: {purl}"); } for (rel, want) in input { - assert_eq!( - after.get(rel), - Some(want), - "{label}: {rel} did not round-trip" - ); + assert_eq!(after.get(rel), Some(want), "{label}: {rel} did not round-trip"); } let extra: Vec<&String> = after.keys().filter(|k| !input.contains_key(*k)).collect(); assert!(extra.is_empty(), "{label}: left behind {extra:?}"); @@ -1253,20 +1110,13 @@ async fn pypi_refusal( PinStatus::Restored => None, }) .collect(); - assert!( - !refusals.is_empty() && refusals.len() == statuses.len(), - "{statuses:?}" - ); + assert!(!refusals.is_empty() && refusals.len() == statuses.len(), "{statuses:?}"); (refusals.join("\n"), rewritten, after) } fn fixture(rel: &str) -> String { - fs::read_to_string( - Path::new(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures") - .join(rel), - ) - .unwrap() + fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures").join(rel)) + .unwrap() } #[tokio::test] @@ -1279,12 +1129,7 @@ async fn requirements_golden_restores_modulo_name_casing() { let (after, statuses) = run_case(&case).await; assert!(!statuses.is_empty()); for (purl, status) in &statuses { - assert_eq!( - *status, - PinStatus::Restored, - "{}: {purl}", - case.dir.display() - ); + assert_eq!(*status, PinStatus::Restored, "{}: {purl}", case.dir.display()); } assert_eq!( after["requirements.txt"].to_ascii_lowercase(), @@ -1304,12 +1149,7 @@ async fn uv_golden_without_a_registry_sibling_is_refused() { let (_server, _env) = pypi_mock(&[( "click", "8.1.7", - vec![( - "click-8.1.7-py3-none-any.whl", - URLLIB3_WHEEL_SHA, - 1, - "2023-08-17T17:29:10Z", - )], + vec![("click-8.1.7-py3-none-any.whl", URLLIB3_WHEEL_SHA, 1, "2023-08-17T17:29:10Z")], )]) .await; let mut ran = 0; @@ -1324,10 +1164,7 @@ async fn uv_golden_without_a_registry_sibling_is_refused() { case.dir.display() ); } - assert_eq!( - after, case.expected, - "a refused pin must leave the files untouched" - ); + assert_eq!(after, case.expected, "a refused pin must leave the files untouched"); ran += 1; } assert!(ran > 0); @@ -1434,14 +1271,9 @@ async fn pdm_static_urls_round_trip() { &format!("{{url = \"{}\"", pypi_file_url(URLLIB3_SDIST)), ); // PDM writes a static_urls entry's files in URL order (sdist first here). - let wheel_line = format!( - " {{url = \"{}\", hash = \"sha256:{URLLIB3_WHEEL_SHA}\"}},\n", - pypi_file_url(URLLIB3_WHEEL) - ); + let wheel_line = format!(" {{url = \"{}\", hash = \"sha256:{URLLIB3_WHEEL_SHA}\"}},\n", pypi_file_url(URLLIB3_WHEEL)); assert!(lock.contains(&wheel_line), "{lock}"); - let lock = - lock.replacen(&wheel_line, "", 1) - .replacen("\n]\n", &format!("\n{wheel_line}]\n"), 1); + let lock = lock.replacen(&wheel_line, "", 1).replacen("\n]\n", &format!("\n{wheel_line}]\n"), 1); assert!( lock.find(URLLIB3_SDIST).unwrap() < lock.find(URLLIB3_WHEEL).unwrap(), "{lock}" @@ -1455,17 +1287,12 @@ async fn pdm_static_urls_round_trip() { async fn pdm_narrowed_lock_with_platform_wheels_is_refused() { let wheel = "urllib3-1.26.18-cp311-cp311-manylinux_2_17_x86_64.whl"; let mut release = urllib3_release(); - release - .2 - .push((wheel, URLLIB3_WHEEL_SHA, 1, "2023-10-17T17:46:21Z")); + release.2.push((wheel, URLLIB3_WHEEL_SHA, 1, "2023-10-17T17:46:21Z")); let (_server, _env) = pypi_mock(&[release]).await; let input = tree(&[("pdm.lock", fixture("pdm-native/2.29.2.lock"))]); let (why, rewritten, after) = pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; - assert!( - why.contains("not derivable") && why.contains("cross_platform"), - "{why}" - ); + assert!(why.contains("not derivable") && why.contains("cross_platform"), "{why}"); assert!(why.contains("git checkout -- pdm.lock"), "{why}"); assert_eq!(after, rewritten); // A cross-platform lock records every file, whatever its tags. @@ -1525,9 +1352,7 @@ async fn pipfile_lock_fixture_and_every_category_round_trip() { assert_pypi_round_trip(&label, &input, &[urllib3_dep()], None).await; } // Pipenv 7.x–2017 writes `path` (and, before 2018, no `index`). - let old = text - .replace(",\n \"index\": \"pypi\"", "") - .replace("\"index\": \"pypi\",\n ", ""); + let old = text.replace(",\n \"index\": \"pypi\"", "").replace("\"index\": \"pypi\",\n ", ""); assert!(!old.contains("\"index\""), "{old}"); let input = tree(&[("Pipfile.lock", old), ("Pipfile", "[packages]\n".into())]); assert_pypi_round_trip("pipenv 2017", &input, &[urllib3_dep()], Some(11)).await; @@ -1561,9 +1386,7 @@ async fn pipfile_lock_real_pipenv_shapes_round_trip_their_index() { let pipfile = fixture(&format!("{dir}/Pipfile")); let pristine: serde_json::Value = serde_json::from_str(&lock).unwrap(); assert_eq!( - pristine["default"]["urllib3"] - .get("index") - .and_then(|v| v.as_str()), + pristine["default"]["urllib3"].get("index").and_then(|v| v.as_str()), index, "{dir}: fixture drifted from what Pipenv writes" ); @@ -1578,16 +1401,9 @@ async fn pipfile_lock_real_pipenv_shapes_round_trip_their_index() { let hosted_lock = hosted(&input, &[urllib3_dep()], major)["Pipfile.lock"].clone(); let entry: serde_json::Value = serde_json::from_str(&hosted_lock).unwrap(); let entry = &entry["default"]["urllib3"]; - assert!( - entry.get("file").is_some() && entry.get("version").is_none(), - "{label}: {entry}" - ); + assert!(entry.get("file").is_some() && entry.get("version").is_none(), "{label}: {entry}"); for key in ["index", "markers", "extras"] { - assert_eq!( - entry.get(key), - pristine["default"]["urllib3"].get(key), - "{label}: {key}" - ); + assert_eq!(entry.get(key), pristine["default"]["urllib3"].get(key), "{label}: {key}"); } assert_pypi_round_trip(&label, &input, &[urllib3_dep()], major).await; } @@ -1611,17 +1427,12 @@ async fn pipfile_lock_marker_excluded_relock_hybrid_restores_the_original() { ("Pipfile", fixture(&format!("{dir}/Pipfile"))), ]); let rewritten = hosted(&input, &[urllib3_dep()], Some(2026)); - let mut relocked: serde_json::Value = serde_json::from_str(&rewritten["Pipfile.lock"]).unwrap(); + let mut relocked: serde_json::Value = + serde_json::from_str(&rewritten["Pipfile.lock"]).unwrap(); let pristine: serde_json::Value = serde_json::from_str(&lock).unwrap(); let entry = relocked["default"]["urllib3"].as_object_mut().unwrap(); - assert!( - entry.contains_key("file") && !entry.contains_key("index"), - "{entry:?}" - ); - entry.insert( - "hashes".into(), - pristine["default"]["urllib3"]["hashes"].clone(), - ); + assert!(entry.contains_key("file") && !entry.contains_key("index"), "{entry:?}"); + entry.insert("hashes".into(), pristine["default"]["urllib3"]["hashes"].clone()); entry.insert("version".into(), serde_json::json!("==1.26.18")); relocked.sort_all_objects(); let hybrid = reindent4(&serde_json::to_string_pretty(&relocked).unwrap()) + "\n"; @@ -1632,10 +1443,7 @@ async fn pipfile_lock_marker_excluded_relock_hybrid_restores_the_original() { for (purl, status) in &statuses { assert_eq!(*status, PinStatus::Restored, "{purl}"); } - assert_eq!( - after["Pipfile.lock"], lock, - "the hybrid restores the pristine bytes" - ); + assert_eq!(after["Pipfile.lock"], lock, "the hybrid restores the pristine bytes"); } #[tokio::test] @@ -1653,10 +1461,7 @@ async fn pipfile_lock_refusals() { ..Default::default() }; let (why, rewritten, after) = pypi_refusal(&input, &[urllib3_dep()], &offline).await; - assert!( - why.contains("offline") && why.contains("git checkout -- Pipfile.lock"), - "{why}" - ); + assert!(why.contains("offline") && why.contains("git checkout -- Pipfile.lock"), "{why}"); assert_eq!(after, rewritten); // A mirror as the only source. let mirror = lock.replace("https://pypi.org/simple", "https://mirror.example/simple"); @@ -1709,10 +1514,7 @@ async fn requirements_hash_mode_ambiguity_is_refused() { let (_server, _env) = pypi_mock(&[urllib3_release()]).await; let input = tree(&[("requirements.txt", "urllib3==1.26.18\n".into())]); let (why, _, _) = pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; - assert!( - why.contains("hash-checking mode") && why.contains("not derivable"), - "{why}" - ); + assert!(why.contains("hash-checking mode") && why.contains("not derivable"), "{why}"); let input = tree(&[( "requirements.txt", "idna==3.4 --hash=sha256:aaaa\nsix==1.16.0\nurllib3==1.26.18\n".into(), @@ -1730,10 +1532,7 @@ async fn requirements_hash_mode_ambiguity_is_refused() { offline: true, ..Default::default() }; - let input = tree(&[( - "requirements.txt", - "flask==2.0.1\nurllib3==1.26.18\n".into(), - )]); + let input = tree(&[("requirements.txt", "flask==2.0.1\nurllib3==1.26.18\n".into())]); let rewritten = hosted(&input, &[urllib3_dep()], None); let (after, statuses) = restore_tree(&rewritten, &offline).await; assert_eq!(statuses[0].1, PinStatus::Restored); @@ -1741,9 +1540,7 @@ async fn requirements_hash_mode_ambiguity_is_refused() { // …and is refused in hash mode. let input = tree(&[( "requirements.txt", - format!( - "idna==3.4 --hash=sha256:aaaa\nurllib3==1.26.18 --hash=sha256:{URLLIB3_WHEEL_SHA}\n" - ), + format!("idna==3.4 --hash=sha256:aaaa\nurllib3==1.26.18 --hash=sha256:{URLLIB3_WHEEL_SHA}\n"), )]); let (why, _, _) = pypi_refusal(&input, &[urllib3_dep()], &offline).await; assert!(why.contains("offline"), "{why}"); @@ -1754,12 +1551,7 @@ async fn requirements_hash_mode_ambiguity_is_refused() { async fn a_refused_pin_leaves_the_other_pins_restored() { // PyPI knows urllib3 only: idna's hashes cannot be re-derived. let (_server, _env) = pypi_mock(&[urllib3_release()]).await; - let idna = pypi_dep( - "idna", - "3.4", - "idna-3.4-py3-none-any.whl", - "44444444-4444-4444-4444-444444444444", - ); + let idna = pypi_dep("idna", "3.4", "idna-3.4-py3-none-any.whl", "44444444-4444-4444-4444-444444444444"); let input = tree(&[( "requirements.txt", format!( @@ -1773,10 +1565,7 @@ async fn a_refused_pin_leaves_the_other_pins_restored() { assert!(matches!(status("pkg:pypi/idna@3.4"), PinStatus::Refused(why) if why.contains("404"))); let lines: Vec<&str> = after["requirements.txt"].lines().collect(); assert_eq!(lines[0], "six==1.16.0 --hash=sha256:aaaa"); - assert!( - lines[1].starts_with("idna @ https://patch.socket.dev/"), - "{lines:?}" - ); + assert!(lines[1].starts_with("idna @ https://patch.socket.dev/"), "{lines:?}"); assert_eq!(lines[2], input["requirements.txt"].lines().nth(2).unwrap()); } @@ -1855,13 +1644,7 @@ async fn uv_project_locks_round_trip() { ("uv.lock", lock.replace('\n', eol)), ("pyproject.toml", pyproject.replace('\n', eol)), ]); - assert_pypi_round_trip( - &format!("uv direct {eol:?}"), - &input, - &[urllib3_dep()], - None, - ) - .await; + assert_pypi_round_trip(&format!("uv direct {eol:?}"), &input, &[urllib3_dep()], None).await; } // A transitive dependency: the override the rewrite pins in the // pyproject and the lock's `[manifest]` both go again. @@ -1969,11 +1752,7 @@ wheels = [{{ url = \"{wheel_url}\", upload-time = 2023-10-17T17:46:21.184Z, size /// microseconds), with (`uv export`) or without (`uv pip compile`) an /// `index` on each registry package. fn uv_pylock(index: bool) -> String { - let index = if index { - "index = \"https://pypi.org/simple\"\n" - } else { - "" - }; + let index = if index { "index = \"https://pypi.org/simple\"\n" } else { "" }; format!( "# This file was autogenerated by uv via the following command:\n\ # uv pip compile --format pylock.toml req.in -o pylock.toml\n\ @@ -2001,13 +1780,8 @@ async fn pylock_without_index_round_trips() { assert!(!lock.contains("index")); for eol in ["\n", "\r\n"] { let input = tree(&[("pylock.toml", lock.replace('\n', eol))]); - assert_pypi_round_trip( - &format!("pip compile {eol:?}"), - &input, - &[urllib3_dep()], - None, - ) - .await; + assert_pypi_round_trip(&format!("pip compile {eol:?}"), &input, &[urllib3_dep()], None) + .await; } // A sibling whose files come from another host is not PyPI. let mirror = lock.replace( @@ -2015,11 +1789,9 @@ async fn pylock_without_index_round_trips() { "https://mirror.example.com/packages/21/ed/", ); let input = tree(&[("pylock.toml", mirror)]); - let (why, _, after) = pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; - assert!( - after["pylock.toml"].contains("patch.socket.dev"), - "the pin stays wired" - ); + let (why, _, after) = + pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; + assert!(after["pylock.toml"].contains("patch.socket.dev"), "the pin stays wired"); assert!(why.contains("not PyPI"), "{why}"); } @@ -2186,10 +1958,7 @@ async fn uv_refusals() { { let (_server, _env) = pypi_mock(&[urllib3_release()]).await; // No other entry shows how this uv joins specifier clauses. - let input = tree(&[ - ("uv.lock", direct.clone()), - ("pyproject.toml", pyproject.into()), - ]); + let input = tree(&[("uv.lock", direct.clone()), ("pyproject.toml", pyproject.into())]); let (why, rewritten, after) = pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; assert!(why.contains("multi-clause"), "{why}"); @@ -2227,12 +1996,7 @@ async fn uv_refusals() { } // A release with interpreter-specific wheels. let mut release = urllib3_release(); - release.2.push(( - "urllib3-1.26.18-cp311-cp311-win_amd64.whl", - URLLIB3_WHEEL_SHA, - 1, - "2023-10-17T17:46:21Z", - )); + release.2.push(("urllib3-1.26.18-cp311-cp311-win_amd64.whl", URLLIB3_WHEEL_SHA, 1, "2023-10-17T17:46:21Z")); let (_server, _env) = pypi_mock(&[release]).await; let input = tree(&[("uv.lock", direct)]); let (why, _, _) = pypi_refusal(&input, &[urllib3_dep()], &RestoreOptions::default()).await; @@ -2286,10 +2050,7 @@ async fn vlt_goldens_round_trip() { // refused a package whose package-lock.json entry the rewrite still // pinned; with vlt-lock.json upstream that pin is not live wiring, so // discovery (rightly) reports no pin to restore there. - let not_invertible = [ - "sibling-package-lock-vlt-installed", - "sibling-refused-in-vlt", - ]; + let not_invertible = ["sibling-package-lock-vlt-installed", "sibling-refused-in-vlt"]; let mut ran = 0; for case in load("npm/vlt") { let name = case.dir.file_name().unwrap().to_string_lossy().into_owned(); @@ -2334,10 +2095,7 @@ async fn maven_config_merge_keeps_the_resolver_lines() { .find(|c| c.dir.ends_with("mvn-config-merge")) .unwrap(); let (after, statuses) = run_case(&case).await; - assert!( - matches!(statuses[..], [(_, PinStatus::Restored)]), - "{statuses:?}" - ); + assert!(matches!(statuses[..], [(_, PinStatus::Restored)]), "{statuses:?}"); for rel in ["pom.xml", ".mvn/checksums/checksums.sha256"] { assert_eq!(after.get(rel), case.input.get(rel), "{rel}"); } @@ -2358,9 +2116,7 @@ async fn nuget_mock(case: &Case) -> MockServer { let (id, version) = (id.to_lowercase(), entry["resolved"].as_str().unwrap()); let catalog = format!("{}/catalog0/data/{id}.{version}.json", server.uri()); Mock::given(method("GET")) - .and(path(format!( - "/v3/registration5-gz-semver2/{id}/{version}.json" - ))) + .and(path(format!("/v3/registration5-gz-semver2/{id}/{version}.json"))) .respond_with( ResponseTemplate::new(200) .set_body_json(serde_json::json!({ "catalogEntry": catalog })), @@ -2430,17 +2186,11 @@ async fn nuget_non_invertible_goldens_restore_or_refuse_as_documented() { let PinStatus::Refused(why) = status else { panic!("{name}: {status:?}"); }; - assert!( - why.contains("corp-feed") && why.contains("git checkout"), - "{why}" - ); + assert!(why.contains("corp-feed") && why.contains("git checkout"), "{why}"); assert_eq!(after, case.expected, "{name}: a refusal changes nothing"); } else { assert_eq!(*status, PinStatus::Restored, "{name}"); - assert_eq!( - after.get("packages.lock.json"), - case.input.get("packages.lock.json") - ); + assert_eq!(after.get("packages.lock.json"), case.input.get("packages.lock.json")); let config = &after["nuget.config"]; assert!(!config.contains("socket-patch") && !config.contains("packageSourceMapping")); } @@ -2478,8 +2228,5 @@ async fn yarn_classic_git_pattern_pin_is_refused() { }, other => panic!("one pin expected: {other:?}"), } - assert_eq!( - fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(), - lock - ); + assert_eq!(fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(), lock); } diff --git a/crates/socket-patch-core/tests/uv_hosted.rs b/crates/socket-patch-core/tests/uv_hosted.rs index 81696f5dc..9a2526cd7 100644 --- a/crates/socket-patch-core/tests/uv_hosted.rs +++ b/crates/socket-patch-core/tests/uv_hosted.rs @@ -1,6 +1,8 @@ use std::collections::BTreeMap; -use socket_patch_core::patch::redirect::{rewrite_registry_redirect, DepOverride, Integrity}; +use socket_patch_core::patch::redirect::{ + rewrite_registry_redirect, DepOverride, Integrity, +}; fn patch(name: &str) -> DepOverride { DepOverride { diff --git a/crates/socket-patch-node/src/lib.rs b/crates/socket-patch-node/src/lib.rs index 29fa8e6ae..d83403b84 100644 --- a/crates/socket-patch-node/src/lib.rs +++ b/crates/socket-patch-node/src/lib.rs @@ -15,11 +15,11 @@ use std::sync::Arc; use napi::bindgen_prelude::{Buffer, External, Function, JsObjectValue, Object, PromiseRaw}; use napi::{Env, Status}; use napi_derive::napi; -use socket_patch_core::api::client::PatchApi; use socket_patch_core::hosted::memory::{ - self as hosted_memory, EngineError, HostedScanOptions, HostedScanOutput, PresentKind, - SelectOptions, SessionBuilder, TreeEntryInput, + self as hosted_memory, EngineError, HostedScanOptions, HostedScanOutput, PresentKind, SelectOptions, + SessionBuilder, TreeEntryInput, }; +use socket_patch_core::api::client::PatchApi; use tokio_util::sync::CancellationToken; use provider::{JsPatchApi, ProviderRefs};