From 5a71a37bbb0529f62d3b554640fc9231638da131 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 07:28:11 +0000 Subject: [PATCH 1/4] Start fix for #632 Assisted-by: Claude Code:claude-opus-5-5 From 7a2287e3f5622214debd7522ebade40605488d43 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 07:36:06 +0000 Subject: [PATCH 2/4] Pin yarn catalog deps in hosted mode A dependency declared "catalog:" in package.json was never patched by scan --mode hosted: the resolutions entry was keyed by the lock's expanded npm: range, but yarn matches resolutions before it expands the catalog. The scan reported success, then yarn install --immutable failed (YN0028) and a plain yarn install kept the unpatched release. Also route name@catalog: / name@catalog: for every .yarnrc.yml catalog that maps the package to a pinned range. A re-run adds the selector to a pin written by an earlier release. Fixes #632 Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-cli/src/commands/apply.rs | 4 +- crates/socket-patch-cli/src/commands/list.rs | 15 +- crates/socket-patch-cli/src/commands/mod.rs | 22 +- .../socket-patch-cli/src/commands/remove.rs | 2 +- .../socket-patch-cli/src/commands/rollback.rs | 11 +- .../src/commands/scan/discovery.rs | 62 ++- .../src/commands/scan/hosted.rs | 54 +- .../socket-patch-cli/src/commands/scan/mod.rs | 85 ++-- .../src/commands/scan/policy.rs | 68 ++- .../src/commands/scan/render.rs | 5 +- .../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 +- .../tests/covgap_commands_scan_mod.rs | 9 +- 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_stale_install.rs | 3 +- .../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_vendor.rs | 10 +- .../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 | 14 +- .../src/formats/cargo/mod.rs | 12 +- .../src/formats/composer/mod.rs | 3 - .../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 +- .../patch/redirect/group_equivalence_tests.rs | 7 +- .../src/patch/redirect/mod.rs | 437 ++++++++++++++-- .../src/patch/redirect/npmrc.rs | 3 - .../src/patch/redirect/pdm.rs | 21 +- .../src/patch/redirect/pipenv.rs | 45 +- .../src/patch/redirect/poetry.rs | 22 +- .../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 +- .../src/patch/redirect/vlt.rs | 1 - 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 +- crates/socket-patch-core/src/utils/hatch.rs | 3 +- .../src/utils/line_endings.rs | 1 - crates/socket-patch-core/src/utils/mod.rs | 2 +- crates/socket-patch-core/src/utils/process.rs | 15 +- .../src/utils/python_script.rs | 7 +- .../src/vendor/bun_lock_text.rs | 1 - .../socket-patch-core/src/vendor/bun_lockb.rs | 9 +- .../src/vendor/cargo_lock.rs | 4 +- .../src/vendor/lock_inventory/view.rs | 4 +- .../src/vendor/lock_inventory/vlt.rs | 2 +- .../src/vendor/lock_inventory/wired.rs | 2 +- .../socket-patch-core/src/vendor/prestage.rs | 5 +- crates/socket-patch-core/src/vendor/pypi.rs | 9 +- .../src/vendor/toml_surgery.rs | 3 +- .../src/vex/discover/cargo.rs | 29 +- .../socket-patch-core/src/vex/discover/gem.rs | 2 +- .../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 | 418 ++++++++++++---- crates/socket-patch-core/tests/uv_hosted.rs | 4 +- crates/socket-patch-node/src/lib.rs | 6 +- 130 files changed, 2476 insertions(+), 845 deletions(-) diff --git a/crates/socket-patch-cli/src/commands/apply.rs b/crates/socket-patch-cli/src/commands/apply.rs index f5918a3dd..4e446491c 100644 --- a/crates/socket-patch-cli/src/commands/apply.rs +++ b/crates/socket-patch-cli/src/commands/apply.rs @@ -2,9 +2,7 @@ use clap::Args; use socket_patch_core::api::blob_fetcher::get_missing_blobs; use socket_patch_core::api::client::{get_api_client_with_overrides, ApiClient}; use socket_patch_core::crawlers::ruby_crawler::config_path_ignored_warning; -use socket_patch_core::crawlers::{ - detect_npm_pkg_manager, Ecosystem, NpmPkgManager, RubyCrawler, -}; +use socket_patch_core::crawlers::{detect_npm_pkg_manager, Ecosystem, NpmPkgManager, RubyCrawler}; use socket_patch_core::manifest::operations::read_manifest; use socket_patch_core::manifest::schema::{PatchFileInfo, PatchManifest, PatchRecord}; use socket_patch_core::patch::apply::{ 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/remove.rs b/crates/socket-patch-cli/src/commands/remove.rs index 0d4be4bb2..143382b02 100644 --- a/crates/socket-patch-cli/src/commands/remove.rs +++ b/crates/socket-patch-cli/src/commands/remove.rs @@ -17,9 +17,9 @@ use super::rollback::{ pin_before_hash_blobs, rollback_patches_inner, run_hosted_leg, sweep_failure, sweep_unused_artifacts, HostedLegOutcome, InnerSelection, }; -use crate::commands::vendored_backend::{RevertedEntry, VendorRevertStep, VendoredBackend}; use crate::args::{apply_env_toggles, GlobalArgs}; use crate::commands::lock_cli::acquire_or_emit; +use crate::commands::vendored_backend::{RevertedEntry, VendorRevertStep, VendoredBackend}; use crate::json_envelope::{Command, Envelope, EnvelopeError, PatchAction, PatchEvent, Status}; use crate::ui::plural; diff --git a/crates/socket-patch-cli/src/commands/rollback.rs b/crates/socket-patch-cli/src/commands/rollback.rs index 5f4191038..36d4b4760 100644 --- a/crates/socket-patch-cli/src/commands/rollback.rs +++ b/crates/socket-patch-cli/src/commands/rollback.rs @@ -10,13 +10,13 @@ use socket_patch_core::manifest::operations::{ }; use socket_patch_core::manifest::schema::{PatchFileInfo, PatchManifest, PatchRecord}; use socket_patch_core::patch::apply::select_installed_variants; +use socket_patch_core::patch::redirect::upstream::HostedPin; use socket_patch_core::patch::rollback::{ cannot_rollback_error, rollback_package_patch, verify_file_rollback, RollbackResult, VerifyRollbackResult, VerifyRollbackStatus, }; use socket_patch_core::telemetry::{track_patch_rollback_failed, track_patch_rolled_back}; use socket_patch_core::utils::purl::{patch_matches, strip_purl_qualifiers}; -use socket_patch_core::patch::redirect::upstream::HostedPin; use socket_patch_core::vendor::{purl_keys_cover, RevertOpts, VendorState}; use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; @@ -1026,7 +1026,8 @@ pub(crate) async fn run_hosted_leg(common: &GlobalArgs, pins: &[HostedPin]) -> H .iter() .map(|(code, detail)| (code.to_string(), detail.clone())), ); - out.edited_files.extend(outcome.reverted_files.iter().cloned()); + out.edited_files + .extend(outcome.reverted_files.iter().cloned()); let unwound: Vec<_> = vlt_targets .into_iter() .filter(|t| out.reverted.iter().any(|p| p == &t.purl)) @@ -1170,7 +1171,11 @@ pub async fn run(args: RollbackArgs) -> i32 { } else if !args.common.silent { println!( "{} the pre-v5 hosted ledger {}: no lockfile pins a hosted patch.", - if args.common.dry_run { "Would remove" } else { "Removed" }, + if args.common.dry_run { + "Would remove" + } else { + "Removed" + }, socket_patch_core::patch::redirect::REDIRECT_STATE_REL ); } diff --git a/crates/socket-patch-cli/src/commands/scan/discovery.rs b/crates/socket-patch-cli/src/commands/scan/discovery.rs index b83f63990..33031413c 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()); @@ -1038,7 +1041,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 @@ -1073,7 +1078,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"] @@ -1176,7 +1183,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 97e6866ce..119b2dc7e 100644 --- a/crates/socket-patch-cli/src/commands/scan/hosted.rs +++ b/crates/socket-patch-cli/src/commands/scan/hosted.rs @@ -932,7 +932,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, @@ -944,6 +945,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. @@ -2304,13 +2306,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 @@ -4391,19 +4399,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/mod.rs b/crates/socket-patch-cli/src/commands/scan/mod.rs index 8ce80d9f1..2674afd7c 100644 --- a/crates/socket-patch-cli/src/commands/scan/mod.rs +++ b/crates/socket-patch-cli/src/commands/scan/mod.rs @@ -35,17 +35,17 @@ use crate::ui::{self, plural, print_json, StatusLine}; use super::get::{download_and_apply_patches_with, DownloadParams, DownloadRun}; -pub use self::socket_yml_args::{SocketYmlArgs, MIN_SEVERITY_ENV}; use self::policy::{load_invocation_policy, InvocationPolicy, PolicyLoadError, ScanPolicy}; +pub use self::socket_yml_args::{SocketYmlArgs, MIN_SEVERITY_ENV}; mod discovery; mod gc; pub(crate) mod hosted; pub(crate) mod policy; -mod socket_yml_args; pub(crate) mod render; pub(crate) mod rollout; pub mod rollout_args; +mod socket_yml_args; pub(crate) mod vendor_flow; use self::discovery::{ @@ -65,13 +65,13 @@ use self::gc::gc_json; pub(crate) use self::hosted::boxed_run_redirect_selected; use self::hosted::run_redirect; pub(crate) use self::hosted::{vlt_rollback_heal, vlt_takeover_heal}; -pub(crate) use self::vendor_flow::{ - boxed_vendor_step, preview_vendor_json, print_dry_run_refusals, VendorStep, -}; use self::vendor_flow::{ boxed_vendor_interactive_path, boxed_vendor_json_path, fold_vendored_skips_into_apply, partition_skipped_selected, }; +pub(crate) use self::vendor_flow::{ + boxed_vendor_step, preview_vendor_json, print_dry_run_refusals, VendorStep, +}; /// Packages per batch request on the authenticated API when `--batch-size` /// is not given: the server's own per-request maximum @@ -318,11 +318,7 @@ pub struct ScanArgs { /// `requests`), or a purl with or without its version /// (`pkg:npm/lodash`, `pkg:pypi/requests@2.31.0`). Repeat the flag or /// separate with commas - #[arg( - long = "package", - env = "SOCKET_SCAN_PACKAGES", - value_delimiter = ',' - )] + #[arg(long = "package", env = "SOCKET_SCAN_PACKAGES", value_delimiter = ',')] pub packages: Vec, /// On a successful scan, also generate an OpenVEX 0.2.0 document. @@ -500,9 +496,10 @@ async fn discover_selected( telemetry.flush().await; let error_count = failures.len(); if error_count > 0 && error_count == packages.len() { - let err = failures - .last() - .map_or_else(|| "all patch-detail queries failed".to_string(), |(_, e)| e.clone()); + let err = failures.last().map_or_else( + || "all patch-detail queries failed".to_string(), + |(_, e)| e.clone(), + ); let message = format!("all {error_count} patch-detail queries failed: {err}"); if detail_error_line { eprintln!("{}", render::fetch_details_failed(&failures)); @@ -568,7 +565,11 @@ fn classified_rows( packages: &[BatchPackagePatches], result: Option<&mut serde_json::Value>, ) -> Vec { - let failed: Vec = discovered.failed.iter().map(|(purl, _)| purl.clone()).collect(); + let failed: Vec = discovered + .failed + .iter() + .map(|(purl, _)| purl.clone()) + .collect(); stage.incomplete = rollout::lookup_incomplete(&recorded.index, &failed, batch_failed); let rows = rollout::classify(&discovered.offers, &recorded.index, &stage.project); if let Some(result) = result { @@ -1317,7 +1318,8 @@ fn project_dirs(cwd: &Path, paths: &[String]) -> Result, St let joined = cwd.join(raw); if raw.contains(['*', '?', '[']) { let pattern = joined.to_string_lossy().into_owned(); - let matches = glob::glob(&pattern).map_err(|e| format!("invalid path pattern `{raw}`: {e}"))?; + let matches = + glob::glob(&pattern).map_err(|e| format!("invalid path pattern `{raw}`: {e}"))?; let before = dirs.len(); dirs.extend( matches @@ -1390,7 +1392,10 @@ async fn run_project_dirs( } // One budget per invocation (§5.2): the directories spend it in sorted // order, and a package admitted in one is admitted free in the next. - let configured = match args.rollout.resolve_from_env(invocation.policy.max_new_patches()) { + let configured = match args + .rollout + .resolve_from_env(invocation.policy.max_new_patches()) + { Ok(max) => max, Err(message) => { eprintln!("Error: {message}"); @@ -1491,7 +1496,10 @@ async fn run_scan( // error. let configured_cap = match args.rollout.carry.as_ref() { Some(carry) => carry.lock().configured, - None => match args.rollout.resolve_from_env(invocation.policy.max_new_patches()) { + None => match args + .rollout + .resolve_from_env(invocation.policy.max_new_patches()) + { Ok(max) => max, Err(message) => { eprintln!("Error: {message}"); @@ -1499,11 +1507,8 @@ async fn run_scan( } }, }; - let mut stage = rollout::Stage::new( - configured_cap, - args.rollout.carry.clone(), - &args.common.cwd, - ); + let mut stage = + rollout::Stage::new(configured_cap, args.rollout.carry.clone(), &args.common.cwd); // Strict airgap (CLI_CONTRACT.md `--offline`): scan's patch discovery // is remote data, so refuse before the crawl and before the API client @@ -1704,8 +1709,11 @@ async fn run_scan( .filter(|pkg| args.common.purl_ecosystem_selected(&pkg.purl)) .collect(); - let package_specs: Vec<&String> = - args.packages.iter().filter(|s| !s.trim().is_empty()).collect(); + let package_specs: Vec<&String> = args + .packages + .iter() + .filter(|s| !s.trim().is_empty()) + .collect(); let filtered_crawled: Vec<_> = if package_specs.is_empty() { filtered_crawled } else { @@ -1860,13 +1868,12 @@ async fn run_scan( // `redirectState` rides the empty-discovery envelope too // (same rule as the ≥1-package path). `wiringLive` is empty // by construction: this run covered zero packages. - let redirect_state = (!args.common.is_global()).then_some( - crate::commands::hosted_state_from_pins( + let redirect_state = + (!args.common.is_global()).then_some(crate::commands::hosted_state_from_pins( &socket_patch_core::patch::redirect::upstream::HostedPin::all( ctx.discovery().await, ), - ), - ); + )); if let Some(state) = redirect_state_json(redirect_state.as_ref(), &[]) { result["redirectState"] = state; } @@ -2222,7 +2229,8 @@ async fn run_scan( // A report-only run selects nothing, but a severity floor or // `enabled: false` still hides candidates; report them like the // human arm does (the detail fetch runs only then). - if !apply && !vendor && policy.reports_selection() && !all_packages_with_patches.is_empty() { + if !apply && !vendor && policy.reports_selection() && !all_packages_with_patches.is_empty() + { if let Err((code, message)) = discover_selected( &api_client, &all_packages_with_patches, @@ -2515,12 +2523,7 @@ async fn run_scan( &all_packages_with_patches, None, ); - updates = offer_updates( - &rows, - &discovered, - &recorded, - &all_packages_with_patches, - ); + updates = offer_updates(&rows, &discovered, &recorded, &all_packages_with_patches); rows } // `discover_selected` already printed the failure to stderr. @@ -2982,14 +2985,20 @@ mod tests { dirs.iter() .map(|(d, explicit)| { ( - d.strip_prefix(tmp.path()).unwrap().to_string_lossy().replace('\\', "/"), + d.strip_prefix(tmp.path()) + .unwrap() + .to_string_lossy() + .replace('\\', "/"), *explicit, ) }) .collect() }; - let got = project_dirs(tmp.path(), &["apps/*".into(), "libs/core".into(), "apps/web".into()]) - .unwrap(); + let got = project_dirs( + tmp.path(), + &["apps/*".into(), "libs/core".into(), "apps/web".into()], + ) + .unwrap(); // Named literally = explicit (also when a glob matches it too). assert_eq!( rel(got), diff --git a/crates/socket-patch-cli/src/commands/scan/policy.rs b/crates/socket-patch-cli/src/commands/scan/policy.rs index 0cd466bf5..21a0e51a6 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/render.rs b/crates/socket-patch-cli/src/commands/scan/render.rs index 2f881da3e..437e80035 100644 --- a/crates/socket-patch-cli/src/commands/scan/render.rs +++ b/crates/socket-patch-cli/src/commands/scan/render.rs @@ -746,7 +746,10 @@ mod tests { #[test] fn report_only_hint_names_agent_mode() { - assert_eq!(report_only_hint()[0], "To apply these patches in place, run:"); + assert_eq!( + report_only_hint()[0], + "To apply these patches in place, run:" + ); assert!(report_only_hint()[1].contains("--mode agent")); } 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 59be95b85..ff8c46a97 100644 --- a/crates/socket-patch-cli/src/commands/vendor.rs +++ b/crates/socket-patch-cli/src/commands/vendor.rs @@ -257,10 +257,7 @@ pub(crate) async fn dispatch_revert_one_opts( /// dependency graph? `None` = cannot determine — callers must keep the /// entry (fail-safe): ecosystems other than npm and cargo 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/covgap_commands_scan_mod.rs b/crates/socket-patch-cli/tests/covgap_commands_scan_mod.rs index 47fa71669..a15170613 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_scan_mod.rs +++ b/crates/socket-patch-cli/tests/covgap_commands_scan_mod.rs @@ -1476,7 +1476,13 @@ async fn scan_hosted_paths_run_once_per_project_directory() { let header = format!("== {} ==", Path::new("apps").join(app).display()); assert!(stdout.contains(&header), "missing {header:?}: {stdout}"); } - assert_eq!(stdout.matches("Switched 0 packages to hosted patches").count(), 2, "{stdout}"); + assert_eq!( + stdout + .matches("Switched 0 packages to hosted patches") + .count(), + 2, + "{stdout}" + ); let reqs = recorded(&mock).await; assert_eq!(batch_bodies(&reqs).len(), 2, "one discovery per directory"); } @@ -1915,7 +1921,6 @@ mod pty { screen.join("\n") ); } - } // --------------------------------------------------------------------------- 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 6f4474404..b6c650cad 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_stale_install.rs b/crates/socket-patch-cli/tests/e2e_redirect_gem_stale_install.rs index 1fed4afd2..3e388d0ec 100644 --- a/crates/socket-patch-cli/tests/e2e_redirect_gem_stale_install.rs +++ b/crates/socket-patch-cli/tests/e2e_redirect_gem_stale_install.rs @@ -327,7 +327,8 @@ async fn gem_hosted_redirect_over_stale_install_warns_loudly() { ); assert_eq!(code, 0, "human re-scan must succeed:\n{stderr}"); assert!( - stderr.contains("Warning: ") && stderr.contains("was switched to its hosted patch, but a stale"), + stderr.contains("Warning: ") + && stderr.contains("was switched to its hosted patch, but a stale"), "human mode must print the stale-install warning on stderr:\n{stderr}" ); assert!( 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 e021d9015..5a412ffe0 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 @@ -574,9 +574,9 @@ async fn berry_hosted_project( 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 761d34ea9..778baf094 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 7ae650809..21ff2fa08 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 ea25f497e..b0ffa2d21 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs @@ -58,7 +58,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. @@ -123,7 +124,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, @@ -372,8 +375,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 @@ -381,13 +391,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). @@ -397,7 +419,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] @@ -420,7 +446,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| { @@ -441,7 +470,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 @@ -469,10 +500,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); } @@ -557,16 +585,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 3c7338d28..eb57fb3b4 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_vendor.rs b/crates/socket-patch-cli/tests/in_process_vendor.rs index 1b6b410fc..b9dd90d0b 100644 --- a/crates/socket-patch-cli/tests/in_process_vendor.rs +++ b/crates/socket-patch-cli/tests/in_process_vendor.rs @@ -1359,16 +1359,16 @@ async fn berry_crlf_takeovers_round_trip_both_directions() { // The vendored `resolutions` entry is gone and the hosted pin (#404 // option C) took its place, in the manifest's own layout: BOM + CRLF. let hosted_pkg = std::fs::read_to_string(root.join("package.json")).unwrap(); - assert!(hosted_pkg.starts_with('\u{feff}'), "BOM kept: {hosted_pkg:?}"); + assert!( + hosted_pkg.starts_with('\u{feff}'), + "BOM kept: {hosted_pkg:?}" + ); let pin_line = format!(" \"left-pad@npm:1.3.0\": \"{hosted_url}\"\r\n"); assert!( hosted_pkg.contains(&pin_line) && !hosted_pkg.contains(".socket/vendor/"), "the hosted pin replaced the vendored resolutions entry: {hosted_pkg:?}" ); - let unpinned = hosted_pkg.replace( - &format!(",\r\n \"resolutions\": {{\r\n{pin_line} }}"), - "", - ); + let unpinned = hosted_pkg.replace(&format!(",\r\n \"resolutions\": {{\r\n{pin_line} }}"), ""); assert_eq!(unpinned, pkg, "nothing else in package.json changed"); let hosted_lock = std::fs::read_to_string(root.join("yarn.lock")).unwrap(); assert!( 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 dfdee4f52..5c0b94c1a 100644 --- a/crates/socket-patch-core/src/crawlers/python_crawler.rs +++ b/crates/socket-patch-core/src/crawlers/python_crawler.rs @@ -365,8 +365,8 @@ async fn find_local_venv_site_packages_with( // it once `poetry env use` recorded an env for the project (see // [`poetry_active_prefix`]). PDM likewise skips an activated venv under // `PDM_IGNORE_ACTIVE_VENV`. - let pdm_ignores_active = pdm_env_flag(var, "PDM_IGNORE_ACTIVE_VENV") - && pdm_drives_project(cwd).await; + let pdm_ignores_active = + pdm_env_flag(var, "PDM_IGNORE_ACTIVE_VENV") && pdm_drives_project(cwd).await; let active_prefix = match &poetry { Some(project) => poetry_active_prefix(project, var), None if pdm_ignores_active => None, @@ -540,9 +540,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() } @@ -2813,7 +2811,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/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 3c345cda8..f0a16029f 100644 --- a/crates/socket-patch-core/src/formats/gem/mod.rs +++ b/crates/socket-patch-core/src/formats/gem/mod.rs @@ -27,7 +27,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`). @@ -208,7 +207,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 04d5e6312..3091134d2 100644 --- a/crates/socket-patch-core/src/formats/registry.rs +++ b/crates/socket-patch-core/src/formats/registry.rs @@ -67,7 +67,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", @@ -118,7 +122,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), @@ -213,7 +221,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 c7f51d5b2..64dbd6d9a 100644 --- a/crates/socket-patch-core/src/formats/yarn/mod.rs +++ b/crates/socket-patch-core/src/formats/yarn/mod.rs @@ -62,7 +62,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 6e5b36e09..9fc5ebfd9 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 e11aa036d..b649621c1 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); @@ -859,8 +876,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() @@ -1112,7 +1132,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 84e0dd8d7..cc4ea8c41 100644 --- a/crates/socket-patch-core/src/hosted/memory/roots.rs +++ b/crates/socket-patch-core/src/hosted/memory/roots.rs @@ -40,17 +40,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(); @@ -211,7 +217,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 f257d0bef..ec2fb15ee 100644 --- a/crates/socket-patch-core/src/lib.rs +++ b/crates/socket-patch-core/src/lib.rs @@ -15,7 +15,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/group_equivalence_tests.rs b/crates/socket-patch-core/src/patch/redirect/group_equivalence_tests.rs index 10fe9d510..480e315e9 100644 --- a/crates/socket-patch-core/src/patch/redirect/group_equivalence_tests.rs +++ b/crates/socket-patch-core/src/patch/redirect/group_equivalence_tests.rs @@ -131,11 +131,12 @@ fn assert_same_with_metadata( bun_lockb_present, python_metadata, ); - let merged = merge_group_outputs(&prefix, run_groups_concurrently(&prefix, &groups)) - .map(|mut merged| { + let merged = merge_group_outputs(&prefix, run_groups_concurrently(&prefix, &groups)).map( + |mut merged| { merged.vlt_drives = vlt::vlt_drives(files, bun_lockb_present); merged - }); + }, + ); assert_eq!( merged.as_ref(), Some(&want), diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index c5b565053..fd821e2a5 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -47,17 +47,18 @@ 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::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; +pub(crate) use crate::formats::yarn::is_berry_lock; +pub(crate) mod hosted_url; #[cfg(test)] mod pnpm_equivalence_tests; mod poetry; @@ -66,18 +67,17 @@ mod python_lock_equivalence_tests; mod requirements; 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)] @@ -3456,6 +3456,9 @@ fn rewrite_yarn_berry( // the hosted tarball (see the section header). Parsed once; written back // in its own layout when a pin changes it. let manifest_text = files.get(BERRY_MANIFEST).map(String::as_str); + // Its `catalog:` / `catalogs:` tables: a dependency declared `catalog:` + // needs a selector yarn matches before it expands the catalog. + let yarnrc = files.get(".yarnrc.yml").map(String::as_str); let mut manifest: Option = manifest_text .and_then(|t| serde_json::from_str::(t.strip_prefix('\u{feff}').unwrap_or(t)).ok()) .filter(Value::is_object); @@ -3771,6 +3774,7 @@ fn rewrite_yarn_berry( }; let pin = match berry_resolutions_pin( manifest_obj, + yarnrc, &fname, &dep.version, &key_ranges, @@ -3822,7 +3826,12 @@ fn rewrite_yarn_berry( 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())), @@ -4007,7 +4016,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)); @@ -4026,6 +4038,17 @@ impl BerryResolutionsPin { /// tarball URL they are empty and recovered from our selectors routed to /// `current_url`. /// +/// Yarn matches `resolutions` against a dependency's descriptor as the +/// manifest spells it, before a `catalog:` descriptor is expanded to its +/// catalog range (#632): a dependency declared `"left-pad": "catalog:"` is +/// never matched by `left-pad@npm:^1.3.0`, though the lock keys its entry by +/// that expanded range. So every `.yarnrc.yml` catalog (`yarnrc`) whose range +/// for `name` is one of the pinned npm ranges is also routed, as +/// `name@catalog:` (the default `catalog:` table) or `name@catalog:` +/// (`catalogs.`). The `npm:` selectors stay: transitive dependents +/// still ask for the expanded range, and rollback rebuilds the lock key from +/// them. +/// /// Refuses (fail closed, nothing written) when the manifest already carries /// a user-authored `resolutions` entry for the package — any selector whose /// target is `name`, bare or scoped — since yarn would apply it alongside @@ -4033,6 +4056,7 @@ impl BerryResolutionsPin { /// user pinned. fn berry_resolutions_pin( manifest: &serde_json::Map, + yarnrc: Option<&str>, name: &str, version: &str, key_ranges: &[String], @@ -4072,7 +4096,7 @@ fn berry_resolutions_pin( ), }); } - let selectors: Vec = if key_ranges.is_empty() { + let mut selectors: Vec = if key_ranges.is_empty() { ours.iter() .filter(|(_, value)| value.as_str().is_some() && value.as_str() == current_url) .map(|(selector, _)| (*selector).clone()) @@ -4092,6 +4116,17 @@ fn berry_resolutions_pin( ), }); } + let ranges: Vec<&str> = selectors + .iter() + .filter_map(|selector| crate::vendor::yarn_classic_lock::split_pattern(selector)) + .filter(|(n, range)| *n == name && range.starts_with("npm:")) + .map(|(_, range)| range) + .collect(); + for selector in berry_catalog_selectors(yarnrc, name, &ranges) { + if !selectors.contains(&selector) { + selectors.push(selector); + } + } let stale = ours .iter() .filter(|(selector, value)| { @@ -4105,6 +4140,53 @@ fn berry_resolutions_pin( Ok(BerryResolutionsPin { selectors, stale }) } +/// The `name@catalog:` / `name@catalog:` selectors of every +/// `.yarnrc.yml` catalog that maps `name` to one of `ranges` (yarn's `npm:` +/// spellings, as the lock keys them). A catalog range is normalized the way +/// yarn normalizes a manifest range: one without a protocol is an `npm:` +/// range. A `.yarnrc.yml` that is absent or not YAML has no catalogs (yarn +/// itself refuses to run on one it cannot parse). +fn berry_catalog_selectors(yarnrc: Option<&str>, name: &str, ranges: &[&str]) -> Vec { + #[derive(serde::Deserialize, Default)] + struct Catalogs { + #[serde(default)] + catalog: Option>, + #[serde(default)] + catalogs: Option>, + } + let Some(rc) = yarnrc else { + return Vec::new(); + }; + let rc = rc.strip_prefix('\u{feff}').unwrap_or(rc); + let Ok(parsed) = serde_saphyr::from_str::>(rc) else { + return Vec::new(); + }; + let parsed = parsed.unwrap_or_default(); + let pins = |table: Option<&serde_json::Map>| { + let range = match table.and_then(|t| t.get(name)) { + Some(Value::String(range)) => range.trim().to_string(), + Some(Value::Number(n)) => n.to_string(), + _ => return false, + }; + let range = if range.contains(':') { + range + } else { + format!("npm:{range}") + }; + ranges.contains(&range.as_str()) + }; + let mut selectors = Vec::new(); + if pins(parsed.catalog.as_ref()) { + selectors.push(format!("{name}@catalog:")); + } + for (catalog, table) in parsed.catalogs.iter().flatten() { + if pins(table.as_object()) { + selectors.push(format!("{name}@catalog:{catalog}")); + } + } + selectors +} + /// Move each entry keyed `moved` to where yarn sorts it. Yarn writes lock /// entries sorted by their (unquoted) key — `__metadata` first — so an entry /// re-keyed from `name@npm:…` to `name@` can move past a sibling (e.g. @@ -4115,7 +4197,11 @@ fn berry_resolutions_pin( /// 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; } @@ -4140,7 +4226,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}, @@ -4711,7 +4796,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 @@ -8141,7 +8225,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()), @@ -8174,8 +8262,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"], @@ -8187,15 +8281,195 @@ 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 ); } + /// A root manifest consuming left-pad through the default catalog. + fn berry_catalog_manifest() -> String { + "{\n \"name\": \"app\",\n \"version\": \"1.0.0\",\n \"dependencies\": {\n \ + \"left-pad\": \"catalog:\"\n }\n}\n" + .to_string() + } + + /// #632: yarn matches `resolutions` against the manifest's `catalog:` + /// descriptor before it expands the catalog, so a pin keyed only by the + /// lock's expanded `left-pad@npm:^1.3.0` never applies to a catalog + /// dependency: `yarn install --immutable` fails YN0028 and a mutable + /// install is unpatched. The catalog's own selector is routed too; the + /// `npm:` one stays for transitive descriptors and for rollback. + #[test] + fn yarn_berry_pin_routes_a_default_catalog_dependency() { + let checksum = format!("10c0/{}", "7".repeat(128)); + let url = berry_hosted_url("left-pad", "left-pad", "1.3.0"); + let ovr = berry_override("left-pad", "1.3.0", &url, &checksum); + for yarnrc in [ + "nodeLinker: node-modules\ncatalog:\n left-pad: ^1.3.0\n", + "\u{feff}catalog:\r\n left-pad: \"npm:^1.3.0\"\r\n", + ] { + let mut files = berry_files(berry_lock("10c0"), berry_catalog_manifest()); + files.insert(".yarnrc.yml".to_string(), yarnrc.to_string()); + let mut r = RewriteResult::default(); + rewrite_yarn_berry(&files, std::slice::from_ref(&ovr), &mut r); + assert!(r.warnings.is_empty(), "{yarnrc:?}: {:?}", r.warnings); + let manifest: Value = serde_json::from_str(&r.files["package.json"]).unwrap(); + assert_eq!( + manifest["resolutions"], + json!({"left-pad@npm:^1.3.0": url, "left-pad@catalog:": url}), + "{yarnrc:?}: {manifest}" + ); + assert_eq!(manifest["dependencies"]["left-pad"], "catalog:"); + let keys: Vec<_> = r + .edits + .iter() + .filter(|e| e.kind == "redirect_yarn_berry_resolution") + .filter_map(|e| e.key.as_deref()) + .collect(); + assert_eq!( + keys, + ["left-pad@npm:^1.3.0", "left-pad@catalog:"], + "{yarnrc:?}" + ); + assert!(r.confirmed_yarn_berry_uuids.contains(&ovr.patch_uuid)); + } + } + + /// #632, workspace shape: the default catalog and a named one + /// (`catalogs.legacy`) both lock into one merged entry, so both catalog + /// selectors are routed; a catalog whose range locks another entry, a + /// catalog of another package and one with a non-npm protocol are not. + #[test] + fn yarn_berry_pin_routes_every_catalog_locking_the_entry() { + let checksum = format!("10c0/{}", "7".repeat(128)); + let url = berry_hosted_url("left-pad", "left-pad", "1.3.0"); + let ovr = berry_override("left-pad", "1.3.0", &url, &checksum); + let lock = format!( + "# header\n\n__metadata:\n version: 8\n cacheKey: 10c0\n\n\ + \"left-pad@npm:1.3.0, left-pad@npm:^1.3.0\":\n version: 1.3.0\n \ + resolution: \"left-pad@npm:1.3.0\"\n checksum: 10c0/{}\n languageName: node\n \ + linkType: hard\n", + "3".repeat(128) + ); + let mut files = berry_files(lock, berry_catalog_manifest()); + files.insert( + ".yarnrc.yml".to_string(), + "catalog:\n left-pad: ^1.3.0\n is-number: 1.3.0\ncatalogs:\n legacy:\n \ + left-pad: 1.3.0\n old:\n left-pad: ^1.0.0\n forked:\n \ + left-pad: \"patch:left-pad@npm%3A1.3.0#./p.patch\"\n" + .to_string(), + ); + let mut r = RewriteResult::default(); + rewrite_yarn_berry(&files, std::slice::from_ref(&ovr), &mut r); + assert!(r.warnings.is_empty(), "{:?}", r.warnings); + let manifest: Value = serde_json::from_str(&r.files["package.json"]).unwrap(); + assert_eq!( + manifest["resolutions"], + json!({ + "left-pad@npm:1.3.0": url, + "left-pad@npm:^1.3.0": url, + "left-pad@catalog:": url, + "left-pad@catalog:legacy": url, + }), + "{manifest}" + ); + } + + /// #632: a re-run over its own catalog pin changes nothing, and a re-run + /// over a pin written before the fix (lock keyed by the URL, only the + /// `npm:` selector) adds the missing catalog selector without touching + /// the lock. + #[test] + fn yarn_berry_catalog_pin_rerun_is_stable_and_heals_an_old_pin() { + let checksum = format!("10c0/{}", "7".repeat(128)); + let url = berry_hosted_url("left-pad", "left-pad", "1.3.0"); + let ovr = berry_override("left-pad", "1.3.0", &url, &checksum); + let yarnrc = "catalog:\n left-pad: ^1.3.0\n"; + let mut files = berry_files(berry_lock("10c0"), berry_catalog_manifest()); + files.insert(".yarnrc.yml".to_string(), yarnrc.to_string()); + let mut first = RewriteResult::default(); + rewrite_yarn_berry(&files, std::slice::from_ref(&ovr), &mut first); + assert!(first.warnings.is_empty(), "{:?}", first.warnings); + let pinned_lock = first.files["yarn.lock"].clone(); + let pinned_manifest = first.files["package.json"].clone(); + + let mut rerun_files = berry_files(pinned_lock.clone(), pinned_manifest); + rerun_files.insert(".yarnrc.yml".to_string(), yarnrc.to_string()); + let mut rerun = RewriteResult::default(); + rewrite_yarn_berry(&rerun_files, std::slice::from_ref(&ovr), &mut rerun); + assert!(rerun.warnings.is_empty(), "{:?}", rerun.warnings); + assert!( + rerun.files.is_empty(), + "re-run is a no-op: {:?}", + rerun.files + ); + assert!(rerun.confirmed_yarn_berry_uuids.contains(&ovr.patch_uuid)); + + let old_manifest = serde_json::to_string_pretty(&json!({ + "name": "app", + "version": "1.0.0", + "dependencies": {"left-pad": "catalog:"}, + "resolutions": {"left-pad@npm:^1.3.0": url}, + })) + .unwrap() + + "\n"; + let mut old_files = berry_files(pinned_lock, old_manifest); + old_files.insert(".yarnrc.yml".to_string(), yarnrc.to_string()); + let mut healed = RewriteResult::default(); + rewrite_yarn_berry(&old_files, std::slice::from_ref(&ovr), &mut healed); + assert!(healed.warnings.is_empty(), "{:?}", healed.warnings); + assert!( + !healed.files.contains_key("yarn.lock"), + "lock already pinned" + ); + let manifest: Value = serde_json::from_str(&healed.files["package.json"]).unwrap(); + assert_eq!( + manifest["resolutions"], + json!({"left-pad@npm:^1.3.0": url, "left-pad@catalog:": url}), + "{manifest}" + ); + } + + /// A user-authored catalog selector is the user's pin: the hosted + /// redirect refuses rather than overwrite it. + #[test] + fn yarn_berry_pin_refuses_a_user_catalog_resolution() { + let checksum = format!("10c0/{}", "7".repeat(128)); + let url = berry_hosted_url("left-pad", "left-pad", "1.3.0"); + let ovr = berry_override("left-pad", "1.3.0", &url, &checksum); + let manifest = serde_json::to_string_pretty(&json!({ + "name": "app", + "dependencies": {"left-pad": "catalog:"}, + "resolutions": {"left-pad@catalog:": "npm:1.3.0"}, + })) + .unwrap(); + let mut files = berry_files(berry_lock("10c0"), manifest); + files.insert( + ".yarnrc.yml".to_string(), + "catalog:\n left-pad: ^1.3.0\n".into(), + ); + let mut r = RewriteResult::default(); + 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::>(), + ["redirect_yarn_berry_resolutions_conflict"] + ); + } + /// The selectors are descriptor-specific: another locked version of the /// same package keeps its registry entry, a multi-range key gets one /// selector per range, and the re-keyed entry moves to where yarn sorts @@ -8225,10 +8499,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![ @@ -8272,7 +8543,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)); @@ -8285,7 +8560,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 @@ -8532,7 +8810,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}"); @@ -8558,10 +8838,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}" ); @@ -8586,12 +8873,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")); @@ -8599,7 +8894,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"] ); @@ -8610,10 +8908,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 @@ -8638,7 +8943,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}" ); @@ -11710,7 +12018,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, @@ -11723,7 +12035,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 @@ -11738,7 +12054,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 ); @@ -12106,11 +12425,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), @@ -13537,7 +13864,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(); @@ -13553,7 +13883,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" + ); } } @@ -15833,7 +16166,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) = ( @@ -15843,15 +16177,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}'), ), 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..608dbfd74 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(); diff --git a/crates/socket-patch-core/src/patch/redirect/pipenv.rs b/crates/socket-patch-core/src/patch/redirect/pipenv.rs index 87bd8ad5c..c1376d4bb 100644 --- a/crates/socket-patch-core/src/patch/redirect/pipenv.rs +++ b/crates/socket-patch-core/src/patch/redirect/pipenv.rs @@ -459,7 +459,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(); @@ -499,20 +502,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")); @@ -570,7 +583,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(); @@ -596,7 +612,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(); @@ -617,7 +636,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. @@ -628,7 +650,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 @@ -702,7 +723,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..b84dde5fa 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,7 +276,11 @@ 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 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 ea0fe324f..f88aeb347 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs @@ -348,9 +348,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 { @@ -678,9 +676,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/patch/redirect/vlt.rs b/crates/socket-patch-core/src/patch/redirect/vlt.rs index 149868520..c3561b44a 100644 --- a/crates/socket-patch-core/src/patch/redirect/vlt.rs +++ b/crates/socket-patch-core/src/patch/redirect/vlt.rs @@ -985,5 +985,4 @@ mod tests { ); assert_eq!(carried_pin_original(&relocked, &old), None); } - } diff --git a/crates/socket-patch-core/src/policy/mod.rs b/crates/socket-patch-core/src/policy/mod.rs index 15778f264..605dfd9ce 100644 --- a/crates/socket-patch-core/src/policy/mod.rs +++ b/crates/socket-patch-core/src/policy/mod.rs @@ -456,7 +456,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 } @@ -805,7 +806,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 5e03be9d7..2c18534f4 100644 --- a/crates/socket-patch-core/src/policy/tests.rs +++ b/crates/socket-patch-core/src/policy/tests.rs @@ -417,8 +417,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] @@ -576,7 +582,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"); } @@ -597,13 +606,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 f176426ce..be1476b19 100644 --- a/crates/socket-patch-core/src/update/download.rs +++ b/crates/socket-patch-core/src/update/download.rs @@ -741,7 +741,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 @@ -757,8 +760,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) @@ -824,7 +826,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 e8f2284fe..973c02877 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); @@ -1608,7 +1608,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")) @@ -1617,7 +1620,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(); @@ -1629,7 +1635,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/hatch.rs b/crates/socket-patch-core/src/utils/hatch.rs index 4be79627a..569727aed 100644 --- a/crates/socket-patch-core/src/utils/hatch.rs +++ b/crates/socket-patch-core/src/utils/hatch.rs @@ -265,8 +265,7 @@ fn rewrite_environments( .is_some_and(|kind| kind != "virtual") { return Err( - "Hatch sources, overrides and custom environments require agent mode" - .into(), + "Hatch sources, overrides and custom environments require agent mode".into(), ); } for key in ["dependencies", "extra-dependencies"] { 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/mod.rs b/crates/socket-patch-core/src/utils/mod.rs index e3ade4d63..e792f2f96 100644 --- a/crates/socket-patch-core/src/utils/mod.rs +++ b/crates/socket-patch-core/src/utils/mod.rs @@ -7,9 +7,9 @@ pub mod env_compat; pub mod failpoint; pub mod fs; pub mod group_commit; -pub mod notice; pub(crate) mod http; pub(crate) mod line_endings; +pub mod notice; pub mod pdm_lock; pub(crate) mod pep440; pub mod pipenv; diff --git a/crates/socket-patch-core/src/utils/process.rs b/crates/socket-patch-core/src/utils/process.rs index 13038c679..a8eb22cd7 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 2ca51e107..5eb997005 100644 --- a/crates/socket-patch-core/src/utils/python_script.rs +++ b/crates/socket-patch-core/src/utils/python_script.rs @@ -627,7 +627,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_lock_text.rs b/crates/socket-patch-core/src/vendor/bun_lock_text.rs index 5a21c5bb4..cbdcc8d93 100644 --- a/crates/socket-patch-core/src/vendor/bun_lock_text.rs +++ b/crates/socket-patch-core/src/vendor/bun_lock_text.rs @@ -557,5 +557,4 @@ mod tests { ); } } - } diff --git a/crates/socket-patch-core/src/vendor/bun_lockb.rs b/crates/socket-patch-core/src/vendor/bun_lockb.rs index 7716a3b32..80c5a9617 100644 --- a/crates/socket-patch-core/src/vendor/bun_lockb.rs +++ b/crates/socket-patch-core/src/vendor/bun_lockb.rs @@ -1867,7 +1867,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 @@ -1910,7 +1913,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/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/vlt.rs b/crates/socket-patch-core/src/vendor/lock_inventory/vlt.rs index f84eed675..ba1324448 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/vlt.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/vlt.rs @@ -6,8 +6,8 @@ use std::path::Path; use serde_json::{Map, Value}; -use crate::constants::npm_family::VLT_LOCK; use super::view::ProjectView; +use crate::constants::npm_family::VLT_LOCK; use crate::vendor::vlt_lock_text::{ is_default_registry, sniff_lock, split_dep_id, DepId, DepIdKind, LockSniff, }; 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 b3149ccaa..b4cf64fb6 100644 --- a/crates/socket-patch-core/src/vendor/prestage.rs +++ b/crates/socket-patch-core/src/vendor/prestage.rs @@ -464,7 +464,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/pypi.rs b/crates/socket-patch-core/src/vendor/pypi.rs index 74883c23e..1f57f74e2 100644 --- a/crates/socket-patch-core/src/vendor/pypi.rs +++ b/crates/socket-patch-core/src/vendor/pypi.rs @@ -3492,8 +3492,13 @@ wheels = [ tokio::fs::remove_dir_all(&uuid_dir).await.unwrap(); let bytes = served_wheel(b"service wheel at another filename"); let server = wiremock::MockServer::start().await; - mount_pypi_granted(&server, "six-1.16.0-py3-none-any.whl", &sri_sha512(&bytes), &bytes) - .await; + mount_pypi_granted( + &server, + "six-1.16.0-py3-none-any.whl", + &sri_sha512(&bytes), + &bytes, + ) + .await; let cfg = pypi_service_cfg(&server.uri(), VendorSource::Service, false); let error = crate::vendor::test_support::expect_failure(vendor(Some(cfg)).await); assert!( 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/gem.rs b/crates/socket-patch-core/src/vex/discover/gem.rs index 77f7388a8..3e0718864 100644 --- a/crates/socket-patch-core/src/vex/discover/gem.rs +++ b/crates/socket-patch-core/src/vex/discover/gem.rs @@ -125,10 +125,10 @@ use super::{ names_vendor_dir, simple_purl, vendor_ref, vendored_leaf_purl, DiscoverCtx, Discovery, PatchedRef, DIAG_LOCKFILE_UNPARSEABLE, DIAG_REF_INVALID, DIAG_REF_UNATTRIBUTABLE, }; -use crate::vendor::gem::{gem_declaration_any, quoted_literal}; use crate::formats::gem::{ bundler_manifest_for, same_remote, GemfileLock, Section, SpecLine, BUNDLER_LOCKS, }; +use crate::vendor::gem::{gem_declaration_any, quoted_literal}; pub(crate) async fn extract(ctx: &DiscoverCtx<'_>, out: &mut Discovery) { // Both locks, legacy spelling first (order only affects diagnostics). diff --git a/crates/socket-patch-core/src/vex/discover/maven.rs b/crates/socket-patch-core/src/vex/discover/maven.rs index 734f3e61d..fc9e1fdb0 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 1e0bc6149..de8dc2e67 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 231d221ba..a8ed7092f 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. @@ -1709,7 +1926,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\ @@ -1737,8 +1958,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( @@ -1746,9 +1972,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}"); } @@ -1774,7 +2002,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}"); @@ -1812,7 +2043,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; @@ -1866,7 +2102,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(); @@ -1911,7 +2150,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}"); } @@ -1932,7 +2174,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 })), @@ -2002,11 +2246,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")); } 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 f3f8ca1d26697a7d143faea019cd3281994ae895 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 07:39:31 +0000 Subject: [PATCH 3/4] Test yarn catalog pins end to end Add a real-yarn check that a fresh checkout of a hosted-pinned catalog dependency installs the patched bytes under --immutable, an in-process scan + rollback round trip, and document catalog pins in the yarn berry hosted notes. Assisted-by: Claude Code:claude-opus-5-5 --- .../tests/e2e_redirect_yarn_berry_build.rs | 85 +++++++++++++++++- .../tests/in_process_redirect.rs | 88 +++++++++++++++++++ docs/ecosystems.md | 5 +- 3 files changed, 173 insertions(+), 5 deletions(-) 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 5a412ffe0..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 @@ -273,6 +273,9 @@ struct BerryRedirectFixture { /// The root `package.json` BEFORE the hosted rewrite (#404 option C /// pins through its `resolutions`, so a revert restores both files). registry_pkg: Vec, + /// The dependency is declared `"catalog:"` (#632): `.yarnrc.yml` keeps + /// the catalog in every checkout. + catalog: bool, _server: MockServer, } @@ -298,6 +301,17 @@ async fn berry_hosted_project( tag: &str, tamper_served_tarball: bool, driver: HostedDriver, +) -> Option { + berry_hosted_project_with(tag, tamper_served_tarball, driver, false).await +} + +/// [`berry_hosted_project`], with the dependency declared through the +/// default yarn catalog when `catalog` is set (#632). +async fn berry_hosted_project_with( + tag: &str, + tamper_served_tarball: bool, + driver: HostedDriver, + catalog: bool, ) -> Option { if !has_corepack_pm(yarn_berry()) { skip!( @@ -317,13 +331,17 @@ async fn berry_hosted_project( std::fs::write( proj.join("package.json"), format!( - r#"{{"name":"redirect-berry-capstone","version":"0.0.0","private":true,"dependencies":{{"{DEP}":"{DEP_VERSION}"}}}}"# + r#"{{"name":"redirect-berry-capstone","version":"0.0.0","private":true,"dependencies":{{"{DEP}":"{}"}}}}"#, + if catalog { "catalog:" } else { DEP_VERSION } ), ) .unwrap(); std::fs::write( proj.join(".yarnrc.yml"), - "nodeLinker: node-modules\nenableGlobalCache: false\n", + format!( + "nodeLinker: node-modules\nenableGlobalCache: false\n{}", + catalog_yarnrc(catalog) + ), ) .unwrap(); @@ -580,6 +598,13 @@ async fn berry_hosted_project( && v.as_str() == Some(hosted_url.as_str()))), "package.json must route {DEP} to the hosted tarball: {root_pkg}" ); + if catalog { + assert_eq!( + root_pkg["resolutions"][format!("{DEP}@catalog:")], + hosted_url.as_str(), + "#632: the catalog descriptor yarn matches is routed too: {root_pkg}" + ); + } assert!( !lock.contains("__archiveUrl"), "the hosted pin must not be an npm: locator; got:\n{lock}" @@ -608,6 +633,7 @@ async fn berry_hosted_project( host, registry_lock, registry_pkg, + catalog, _server: server, }) } @@ -620,11 +646,21 @@ fn fresh_yarnrc(fx: &BerryRedirectFixture) -> String { format!( "nodeLinker: node-modules\nenableGlobalCache: false\n\ unsafeHttpWhitelist:\n - \"{}\"\n\ - npmRegistryServer: \"http://127.0.0.1:1\"\n", - fx.host.split(':').next().unwrap_or("127.0.0.1") + npmRegistryServer: \"http://127.0.0.1:1\"\n{}", + fx.host.split(':').next().unwrap_or("127.0.0.1"), + catalog_yarnrc(fx.catalog) ) } +/// The `.yarnrc.yml` default catalog of a `"catalog:"` fixture (#632). +fn catalog_yarnrc(catalog: bool) -> String { + if catalog { + format!("catalog:\n {DEP}: {DEP_VERSION}\n") + } else { + String::new() + } +} + /// Fresh dir with only the committable files, then `yarn install --immutable /// --check-cache` offline-from-registry (the wiremock host is whitelisted for /// http). The install runs with an npm registry token that yarn must apply to @@ -790,6 +826,47 @@ async fn berry_redirect_fresh_checkout_installs_patched_bytes() { hosted_manifestless_vex_matrix(&fx, HostedDriver::Scan); } +/// #632: a dependency declared through a yarn catalog (`"catalog:"`, yarn +/// >= 4.10). Yarn matches `resolutions` before it expands the catalog, so a +/// pin routing only the expanded `npm:` descriptor left the fresh +/// `--immutable` install failing YN0028 (and a mutable one unpatched). The +/// fresh checkout must install the patched bytes from the hosted tarball. +#[tokio::test(flavor = "multi_thread")] +#[serial_test::serial] +async fn berry_redirect_catalog_dependency_fresh_checkout_installs() { + let release = yarn_berry().strip_prefix("yarn@").unwrap_or(yarn_berry()); + let minor: Vec = release + .split('.') + .take(2) + .filter_map(|p| p.parse().ok()) + .collect(); + if minor.as_slice() < [4, 10].as_slice() { + // Not a skip of an available toolchain: catalogs do not exist before + // yarn 4.10, so there is nothing to exercise. + println!("SKIP berry catalog e2e: {release} predates yarn catalogs (4.10)"); + return; + } + let Some(fx) = berry_hosted_project_with("catalog", false, HostedDriver::Scan, true).await + else { + return; + }; + + let (fresh, ci) = fresh_checkout_yarn_install(&fx); + assert!( + ci.status.success(), + "fresh-checkout `yarn install --immutable --check-cache` of a catalog dependency \ + must succeed from the hosted patch tarball.\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&ci.stdout), + String::from_utf8_lossy(&ci.stderr), + ); + let installed = std::fs::read(fresh.join("node_modules").join(DEP).join("index.js")).unwrap(); + assert_eq!( + installed, fx.patched, + "fresh install of the catalog dependency must be the patched content" + ); + assert_patch_host_got_no_auth(&fx).await; +} + /// get-driven hosted twin (v4.0): `get --mode hosted --json --yes` /// routes through the SAME hosted engine as `scan --mode hosted`, so the /// berry chain must hold unchanged — including the `10c0` cacheKey bootstrap diff --git a/crates/socket-patch-cli/tests/in_process_redirect.rs b/crates/socket-patch-cli/tests/in_process_redirect.rs index 21ff2fa08..57323d7fb 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect.rs @@ -863,6 +863,94 @@ async fn scan_redirect_rewrites_crlf_and_bom_yarn_berry_locks_and_rollback_resto } } +/// #632: a dependency declared through a yarn catalog (`"catalog:"`) is +/// matched by yarn's `resolutions` before the catalog is expanded, so the +/// hosted pin must also route `@catalog:`; `rollback` must drop every +/// selector it wrote and restore the lock's expanded `npm:` key, leaving +/// package.json byte-identical to the pristine one. +#[tokio::test] +#[serial] +async fn yarn_berry_catalog_dependency_is_pinned_and_rolled_back() { + let server = MockServer::start().await; + mock_discovery(&server).await; + let hosted_url = HOSTED_URL.replace("http://patch.test", &server.uri()); + mock_reference_with_berry_url(&server, &hosted_url).await; + mock_view(&server).await; + let tarball = upstream_tarball(); + mock_npm_registry( + &server, + &vlt_hosted_common::sha512_sri(&tarball), + Some(tarball), + ) + .await; + + let tmp = tempfile::tempdir().unwrap(); + write_berry_project(tmp.path()); + let pkg_path = tmp.path().join("package.json"); + std::fs::write( + &pkg_path, + format!( + "{{\n \"name\": \"consumer\",\n \"version\": \"0.0.0\",\n \ + \"dependencies\": {{\n \"{NAME}\": \"catalog:\"\n }}\n}}\n" + ), + ) + .unwrap(); + std::fs::write( + tmp.path().join(".yarnrc.yml"), + format!("nodeLinker: node-modules\ncatalog:\n {NAME}: ^{VERSION}\n"), + ) + .unwrap(); + let pristine_pkg = std::fs::read_to_string(&pkg_path).unwrap(); + let lock_path = tmp.path().join("yarn.lock"); + let pristine_lock = std::fs::read_to_string(&lock_path).unwrap(); + + let env = run_redirect_subprocess_with( + tmp.path(), + &server.uri(), + &["--patch-server-url", &server.uri()], + ); + assert_eq!(env["redirect"]["redirected"], 1, "{env:#}"); + assert!(warning_codes(&env).is_empty(), "{env:#}"); + let pkg: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&pkg_path).unwrap()).unwrap(); + assert_eq!( + pkg["resolutions"], + serde_json::json!({ + format!("{NAME}@npm:^{VERSION}"): hosted_url, + format!("{NAME}@catalog:"): hosted_url, + }), + "{pkg}" + ); + let lock = std::fs::read_to_string(&lock_path).unwrap(); + assert!( + lock.contains(&format!("\"{NAME}@{hosted_url}\":")), + "the entry is keyed by the tarball descriptor: {lock}" + ); + + let (code, env) = rollback_json_with_origin(tmp.path(), &server, &server.uri()); + assert_eq!(code, Some(0), "rollback: {env:#}"); + assert_eq!( + env["hosted"]["reverted"], + serde_json::json!([PURL]), + "{env:#}" + ); + assert_eq!( + std::fs::read_to_string(&pkg_path).unwrap(), + pristine_pkg, + "rollback drops both selectors" + ); + let restored = std::fs::read_to_string(&lock_path).unwrap(); + let checksum = berry_checksum_of(&restored); + assert_eq!( + restored, + pristine_lock.replace( + &format!("10c0/{}", "3".repeat(128)), + &format!("10c0/{checksum}") + ), + "rollback restores the expanded npm: key" + ); +} + /// #404 upgrade path: a lock pinned by an earlier release carries the old /// `npm:::__archiveUrl=` resolution, which makes yarn's npm fetcher /// send registry auth to the patch host. `rollback` must still recognize and diff --git a/docs/ecosystems.md b/docs/ecosystems.md index 1beb7b91d..61ed68964 100644 --- a/docs/ecosystems.md +++ b/docs/ecosystems.md @@ -78,7 +78,10 @@ The backticked slug in each row is the value `-e`/`--ecosystems` accepts (e.g. - **yarn berry** — the redirect pins the way yarn does for a root `resolutions` entry (cacheKey `10c0` / yarn 4): `package.json` routes the locked descriptor (`"left-pad@npm:^1.3.0"`) to the hosted tarball and only that `yarn.lock` entry - is re-keyed by it. Yarn then fetches it without npm registry credentials and + is re-keyed by it. A dependency declared through a yarn catalog (`"catalog:"`, + yarn 4.10+) is matched before yarn expands the catalog, so each `.yarnrc.yml` + catalog that resolves to the locked range is routed too (`"left-pad@catalog:"`, + `"left-pad@catalog:"`). Yarn then fetches it without npm registry credentials and hardened mode accepts it. A user-authored `resolutions` entry for the package is never overwritten (`redirect_yarn_berry_resolutions_conflict`), and `.yarnrc.yml`'s `compressionLevel` must stay 0. The node-modules linker From 55dc0cbf1e7fc90414156d8130422790b9789377 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 08:18:36 +0000 Subject: [PATCH 4/4] Drop unrelated rustfmt churn cargo fmt --all also reformatted 127 files this fix doesn't touch (main isn't rustfmt-clean). Restore them and the untouched hunks of the edited files to main, so the PR only carries the catalog fix, its tests and the docs note. Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-cli/src/commands/apply.rs | 4 +- crates/socket-patch-cli/src/commands/list.rs | 15 +- crates/socket-patch-cli/src/commands/mod.rs | 22 +- .../socket-patch-cli/src/commands/remove.rs | 2 +- .../socket-patch-cli/src/commands/rollback.rs | 11 +- .../src/commands/scan/discovery.rs | 62 +-- .../src/commands/scan/hosted.rs | 54 +- .../socket-patch-cli/src/commands/scan/mod.rs | 85 ++-- .../src/commands/scan/policy.rs | 68 +-- .../src/commands/scan/render.rs | 5 +- .../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 +- .../tests/covgap_commands_scan_mod.rs | 9 +- 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_stale_install.rs | 3 +- .../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 | 9 +- .../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_vendor.rs | 10 +- .../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 | 14 +- .../src/formats/cargo/mod.rs | 12 +- .../src/formats/composer/mod.rs | 3 + .../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 +- .../patch/redirect/group_equivalence_tests.rs | 7 +- .../src/patch/redirect/mod.rs | 187 ++----- .../src/patch/redirect/npmrc.rs | 3 + .../src/patch/redirect/pdm.rs | 21 +- .../src/patch/redirect/pipenv.rs | 45 +- .../src/patch/redirect/poetry.rs | 22 +- .../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 +- .../src/patch/redirect/vlt.rs | 1 + 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 +- crates/socket-patch-core/src/utils/hatch.rs | 3 +- .../src/utils/line_endings.rs | 1 + crates/socket-patch-core/src/utils/mod.rs | 2 +- crates/socket-patch-core/src/utils/process.rs | 15 +- .../src/utils/python_script.rs | 7 +- .../src/vendor/bun_lock_text.rs | 1 + .../socket-patch-core/src/vendor/bun_lockb.rs | 9 +- .../src/vendor/cargo_lock.rs | 4 +- .../src/vendor/lock_inventory/view.rs | 4 +- .../src/vendor/lock_inventory/vlt.rs | 2 +- .../src/vendor/lock_inventory/wired.rs | 2 +- .../socket-patch-core/src/vendor/prestage.rs | 5 +- crates/socket-patch-core/src/vendor/pypi.rs | 9 +- .../src/vendor/toml_surgery.rs | 3 +- .../src/vex/discover/cargo.rs | 29 +- .../socket-patch-core/src/vex/discover/gem.rs | 2 +- .../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 | 418 ++++------------ crates/socket-patch-core/tests/uv_hosted.rs | 4 +- crates/socket-patch-node/src/lib.rs | 6 +- 130 files changed, 845 insertions(+), 2228 deletions(-) diff --git a/crates/socket-patch-cli/src/commands/apply.rs b/crates/socket-patch-cli/src/commands/apply.rs index 4e446491c..f5918a3dd 100644 --- a/crates/socket-patch-cli/src/commands/apply.rs +++ b/crates/socket-patch-cli/src/commands/apply.rs @@ -2,7 +2,9 @@ use clap::Args; use socket_patch_core::api::blob_fetcher::get_missing_blobs; use socket_patch_core::api::client::{get_api_client_with_overrides, ApiClient}; use socket_patch_core::crawlers::ruby_crawler::config_path_ignored_warning; -use socket_patch_core::crawlers::{detect_npm_pkg_manager, Ecosystem, NpmPkgManager, RubyCrawler}; +use socket_patch_core::crawlers::{ + detect_npm_pkg_manager, Ecosystem, NpmPkgManager, RubyCrawler, +}; use socket_patch_core::manifest::operations::read_manifest; use socket_patch_core::manifest::schema::{PatchFileInfo, PatchManifest, PatchRecord}; use socket_patch_core::patch::apply::{ 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/remove.rs b/crates/socket-patch-cli/src/commands/remove.rs index 143382b02..0d4be4bb2 100644 --- a/crates/socket-patch-cli/src/commands/remove.rs +++ b/crates/socket-patch-cli/src/commands/remove.rs @@ -17,9 +17,9 @@ use super::rollback::{ pin_before_hash_blobs, rollback_patches_inner, run_hosted_leg, sweep_failure, sweep_unused_artifacts, HostedLegOutcome, InnerSelection, }; +use crate::commands::vendored_backend::{RevertedEntry, VendorRevertStep, VendoredBackend}; use crate::args::{apply_env_toggles, GlobalArgs}; use crate::commands::lock_cli::acquire_or_emit; -use crate::commands::vendored_backend::{RevertedEntry, VendorRevertStep, VendoredBackend}; use crate::json_envelope::{Command, Envelope, EnvelopeError, PatchAction, PatchEvent, Status}; use crate::ui::plural; diff --git a/crates/socket-patch-cli/src/commands/rollback.rs b/crates/socket-patch-cli/src/commands/rollback.rs index 36d4b4760..5f4191038 100644 --- a/crates/socket-patch-cli/src/commands/rollback.rs +++ b/crates/socket-patch-cli/src/commands/rollback.rs @@ -10,13 +10,13 @@ use socket_patch_core::manifest::operations::{ }; use socket_patch_core::manifest::schema::{PatchFileInfo, PatchManifest, PatchRecord}; use socket_patch_core::patch::apply::select_installed_variants; -use socket_patch_core::patch::redirect::upstream::HostedPin; use socket_patch_core::patch::rollback::{ cannot_rollback_error, rollback_package_patch, verify_file_rollback, RollbackResult, VerifyRollbackResult, VerifyRollbackStatus, }; use socket_patch_core::telemetry::{track_patch_rollback_failed, track_patch_rolled_back}; use socket_patch_core::utils::purl::{patch_matches, strip_purl_qualifiers}; +use socket_patch_core::patch::redirect::upstream::HostedPin; use socket_patch_core::vendor::{purl_keys_cover, RevertOpts, VendorState}; use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; @@ -1026,8 +1026,7 @@ pub(crate) async fn run_hosted_leg(common: &GlobalArgs, pins: &[HostedPin]) -> H .iter() .map(|(code, detail)| (code.to_string(), detail.clone())), ); - out.edited_files - .extend(outcome.reverted_files.iter().cloned()); + out.edited_files.extend(outcome.reverted_files.iter().cloned()); let unwound: Vec<_> = vlt_targets .into_iter() .filter(|t| out.reverted.iter().any(|p| p == &t.purl)) @@ -1171,11 +1170,7 @@ pub async fn run(args: RollbackArgs) -> i32 { } else if !args.common.silent { println!( "{} the pre-v5 hosted ledger {}: no lockfile pins a hosted patch.", - if args.common.dry_run { - "Would remove" - } else { - "Removed" - }, + if args.common.dry_run { "Would remove" } else { "Removed" }, socket_patch_core::patch::redirect::REDIRECT_STATE_REL ); } diff --git a/crates/socket-patch-cli/src/commands/scan/discovery.rs b/crates/socket-patch-cli/src/commands/scan/discovery.rs index 33031413c..b83f63990 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()); @@ -1041,9 +1038,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 @@ -1078,9 +1073,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"] @@ -1183,10 +1176,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 119b2dc7e..97e6866ce 100644 --- a/crates/socket-patch-cli/src/commands/scan/hosted.rs +++ b/crates/socket-patch-cli/src/commands/scan/hosted.rs @@ -932,8 +932,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, @@ -945,7 +944,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. @@ -2306,19 +2304,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 @@ -4399,43 +4391,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/mod.rs b/crates/socket-patch-cli/src/commands/scan/mod.rs index 2674afd7c..8ce80d9f1 100644 --- a/crates/socket-patch-cli/src/commands/scan/mod.rs +++ b/crates/socket-patch-cli/src/commands/scan/mod.rs @@ -35,17 +35,17 @@ use crate::ui::{self, plural, print_json, StatusLine}; use super::get::{download_and_apply_patches_with, DownloadParams, DownloadRun}; -use self::policy::{load_invocation_policy, InvocationPolicy, PolicyLoadError, ScanPolicy}; pub use self::socket_yml_args::{SocketYmlArgs, MIN_SEVERITY_ENV}; +use self::policy::{load_invocation_policy, InvocationPolicy, PolicyLoadError, ScanPolicy}; mod discovery; mod gc; pub(crate) mod hosted; pub(crate) mod policy; +mod socket_yml_args; pub(crate) mod render; pub(crate) mod rollout; pub mod rollout_args; -mod socket_yml_args; pub(crate) mod vendor_flow; use self::discovery::{ @@ -65,13 +65,13 @@ use self::gc::gc_json; pub(crate) use self::hosted::boxed_run_redirect_selected; use self::hosted::run_redirect; pub(crate) use self::hosted::{vlt_rollback_heal, vlt_takeover_heal}; +pub(crate) use self::vendor_flow::{ + boxed_vendor_step, preview_vendor_json, print_dry_run_refusals, VendorStep, +}; use self::vendor_flow::{ boxed_vendor_interactive_path, boxed_vendor_json_path, fold_vendored_skips_into_apply, partition_skipped_selected, }; -pub(crate) use self::vendor_flow::{ - boxed_vendor_step, preview_vendor_json, print_dry_run_refusals, VendorStep, -}; /// Packages per batch request on the authenticated API when `--batch-size` /// is not given: the server's own per-request maximum @@ -318,7 +318,11 @@ pub struct ScanArgs { /// `requests`), or a purl with or without its version /// (`pkg:npm/lodash`, `pkg:pypi/requests@2.31.0`). Repeat the flag or /// separate with commas - #[arg(long = "package", env = "SOCKET_SCAN_PACKAGES", value_delimiter = ',')] + #[arg( + long = "package", + env = "SOCKET_SCAN_PACKAGES", + value_delimiter = ',' + )] pub packages: Vec, /// On a successful scan, also generate an OpenVEX 0.2.0 document. @@ -496,10 +500,9 @@ async fn discover_selected( telemetry.flush().await; let error_count = failures.len(); if error_count > 0 && error_count == packages.len() { - let err = failures.last().map_or_else( - || "all patch-detail queries failed".to_string(), - |(_, e)| e.clone(), - ); + let err = failures + .last() + .map_or_else(|| "all patch-detail queries failed".to_string(), |(_, e)| e.clone()); let message = format!("all {error_count} patch-detail queries failed: {err}"); if detail_error_line { eprintln!("{}", render::fetch_details_failed(&failures)); @@ -565,11 +568,7 @@ fn classified_rows( packages: &[BatchPackagePatches], result: Option<&mut serde_json::Value>, ) -> Vec { - let failed: Vec = discovered - .failed - .iter() - .map(|(purl, _)| purl.clone()) - .collect(); + let failed: Vec = discovered.failed.iter().map(|(purl, _)| purl.clone()).collect(); stage.incomplete = rollout::lookup_incomplete(&recorded.index, &failed, batch_failed); let rows = rollout::classify(&discovered.offers, &recorded.index, &stage.project); if let Some(result) = result { @@ -1318,8 +1317,7 @@ fn project_dirs(cwd: &Path, paths: &[String]) -> Result, St let joined = cwd.join(raw); if raw.contains(['*', '?', '[']) { let pattern = joined.to_string_lossy().into_owned(); - let matches = - glob::glob(&pattern).map_err(|e| format!("invalid path pattern `{raw}`: {e}"))?; + let matches = glob::glob(&pattern).map_err(|e| format!("invalid path pattern `{raw}`: {e}"))?; let before = dirs.len(); dirs.extend( matches @@ -1392,10 +1390,7 @@ async fn run_project_dirs( } // One budget per invocation (§5.2): the directories spend it in sorted // order, and a package admitted in one is admitted free in the next. - let configured = match args - .rollout - .resolve_from_env(invocation.policy.max_new_patches()) - { + let configured = match args.rollout.resolve_from_env(invocation.policy.max_new_patches()) { Ok(max) => max, Err(message) => { eprintln!("Error: {message}"); @@ -1496,10 +1491,7 @@ async fn run_scan( // error. let configured_cap = match args.rollout.carry.as_ref() { Some(carry) => carry.lock().configured, - None => match args - .rollout - .resolve_from_env(invocation.policy.max_new_patches()) - { + None => match args.rollout.resolve_from_env(invocation.policy.max_new_patches()) { Ok(max) => max, Err(message) => { eprintln!("Error: {message}"); @@ -1507,8 +1499,11 @@ async fn run_scan( } }, }; - let mut stage = - rollout::Stage::new(configured_cap, args.rollout.carry.clone(), &args.common.cwd); + let mut stage = rollout::Stage::new( + configured_cap, + args.rollout.carry.clone(), + &args.common.cwd, + ); // Strict airgap (CLI_CONTRACT.md `--offline`): scan's patch discovery // is remote data, so refuse before the crawl and before the API client @@ -1709,11 +1704,8 @@ async fn run_scan( .filter(|pkg| args.common.purl_ecosystem_selected(&pkg.purl)) .collect(); - let package_specs: Vec<&String> = args - .packages - .iter() - .filter(|s| !s.trim().is_empty()) - .collect(); + let package_specs: Vec<&String> = + args.packages.iter().filter(|s| !s.trim().is_empty()).collect(); let filtered_crawled: Vec<_> = if package_specs.is_empty() { filtered_crawled } else { @@ -1868,12 +1860,13 @@ async fn run_scan( // `redirectState` rides the empty-discovery envelope too // (same rule as the ≥1-package path). `wiringLive` is empty // by construction: this run covered zero packages. - let redirect_state = - (!args.common.is_global()).then_some(crate::commands::hosted_state_from_pins( + let redirect_state = (!args.common.is_global()).then_some( + crate::commands::hosted_state_from_pins( &socket_patch_core::patch::redirect::upstream::HostedPin::all( ctx.discovery().await, ), - )); + ), + ); if let Some(state) = redirect_state_json(redirect_state.as_ref(), &[]) { result["redirectState"] = state; } @@ -2229,8 +2222,7 @@ async fn run_scan( // A report-only run selects nothing, but a severity floor or // `enabled: false` still hides candidates; report them like the // human arm does (the detail fetch runs only then). - if !apply && !vendor && policy.reports_selection() && !all_packages_with_patches.is_empty() - { + if !apply && !vendor && policy.reports_selection() && !all_packages_with_patches.is_empty() { if let Err((code, message)) = discover_selected( &api_client, &all_packages_with_patches, @@ -2523,7 +2515,12 @@ async fn run_scan( &all_packages_with_patches, None, ); - updates = offer_updates(&rows, &discovered, &recorded, &all_packages_with_patches); + updates = offer_updates( + &rows, + &discovered, + &recorded, + &all_packages_with_patches, + ); rows } // `discover_selected` already printed the failure to stderr. @@ -2985,20 +2982,14 @@ mod tests { dirs.iter() .map(|(d, explicit)| { ( - d.strip_prefix(tmp.path()) - .unwrap() - .to_string_lossy() - .replace('\\', "/"), + d.strip_prefix(tmp.path()).unwrap().to_string_lossy().replace('\\', "/"), *explicit, ) }) .collect() }; - let got = project_dirs( - tmp.path(), - &["apps/*".into(), "libs/core".into(), "apps/web".into()], - ) - .unwrap(); + let got = project_dirs(tmp.path(), &["apps/*".into(), "libs/core".into(), "apps/web".into()]) + .unwrap(); // Named literally = explicit (also when a glob matches it too). assert_eq!( rel(got), diff --git a/crates/socket-patch-cli/src/commands/scan/policy.rs b/crates/socket-patch-cli/src/commands/scan/policy.rs index 21a0e51a6..0cd466bf5 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/render.rs b/crates/socket-patch-cli/src/commands/scan/render.rs index 437e80035..2f881da3e 100644 --- a/crates/socket-patch-cli/src/commands/scan/render.rs +++ b/crates/socket-patch-cli/src/commands/scan/render.rs @@ -746,10 +746,7 @@ mod tests { #[test] fn report_only_hint_names_agent_mode() { - assert_eq!( - report_only_hint()[0], - "To apply these patches in place, run:" - ); + assert_eq!(report_only_hint()[0], "To apply these patches in place, run:"); assert!(report_only_hint()[1].contains("--mode agent")); } 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 ff8c46a97..59be95b85 100644 --- a/crates/socket-patch-cli/src/commands/vendor.rs +++ b/crates/socket-patch-cli/src/commands/vendor.rs @@ -257,7 +257,10 @@ pub(crate) async fn dispatch_revert_one_opts( /// dependency graph? `None` = cannot determine — callers must keep the /// entry (fail-safe): ecosystems other than npm and cargo 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/covgap_commands_scan_mod.rs b/crates/socket-patch-cli/tests/covgap_commands_scan_mod.rs index a15170613..47fa71669 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_scan_mod.rs +++ b/crates/socket-patch-cli/tests/covgap_commands_scan_mod.rs @@ -1476,13 +1476,7 @@ async fn scan_hosted_paths_run_once_per_project_directory() { let header = format!("== {} ==", Path::new("apps").join(app).display()); assert!(stdout.contains(&header), "missing {header:?}: {stdout}"); } - assert_eq!( - stdout - .matches("Switched 0 packages to hosted patches") - .count(), - 2, - "{stdout}" - ); + assert_eq!(stdout.matches("Switched 0 packages to hosted patches").count(), 2, "{stdout}"); let reqs = recorded(&mock).await; assert_eq!(batch_bodies(&reqs).len(), 2, "one discovery per directory"); } @@ -1921,6 +1915,7 @@ mod pty { screen.join("\n") ); } + } // --------------------------------------------------------------------------- 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 b6c650cad..6f4474404 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_stale_install.rs b/crates/socket-patch-cli/tests/e2e_redirect_gem_stale_install.rs index 3e388d0ec..1fed4afd2 100644 --- a/crates/socket-patch-cli/tests/e2e_redirect_gem_stale_install.rs +++ b/crates/socket-patch-cli/tests/e2e_redirect_gem_stale_install.rs @@ -327,8 +327,7 @@ async fn gem_hosted_redirect_over_stale_install_warns_loudly() { ); assert_eq!(code, 0, "human re-scan must succeed:\n{stderr}"); assert!( - stderr.contains("Warning: ") - && stderr.contains("was switched to its hosted patch, but a stale"), + stderr.contains("Warning: ") && stderr.contains("was switched to its hosted patch, but a stale"), "human mode must print the stale-install warning on stderr:\n{stderr}" ); assert!( 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 778baf094..761d34ea9 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 57323d7fb..a0a5b427f 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}" @@ -863,6 +862,7 @@ async fn scan_redirect_rewrites_crlf_and_bom_yarn_berry_locks_and_rollback_resto } } +/// #404 upgrade path: a lock pinned by an earlier release carries the old /// #632: a dependency declared through a yarn catalog (`"catalog:"`) is /// matched by yarn's `resolutions` before the catalog is expanded, so the /// hosted pin must also route `@catalog:`; `rollback` must drop every @@ -951,7 +951,6 @@ async fn yarn_berry_catalog_dependency_is_pinned_and_rolled_back() { ); } -/// #404 upgrade path: a lock pinned by an earlier release carries the old /// `npm:::__archiveUrl=` resolution, which makes yarn's npm fetcher /// send registry auth to the patch host. `rollback` must still recognize and /// restore that legacy pin, and a repeat hosted `scan` must re-pin it to the 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 b0ffa2d21..ea25f497e 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect_pipenv.rs @@ -58,8 +58,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. @@ -124,9 +123,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, @@ -375,15 +372,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 @@ -391,25 +381,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). @@ -419,11 +397,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] @@ -446,10 +420,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| { @@ -470,9 +441,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 @@ -500,7 +469,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); } @@ -585,27 +557,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 eb57fb3b4..3c7338d28 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_vendor.rs b/crates/socket-patch-cli/tests/in_process_vendor.rs index b9dd90d0b..1b6b410fc 100644 --- a/crates/socket-patch-cli/tests/in_process_vendor.rs +++ b/crates/socket-patch-cli/tests/in_process_vendor.rs @@ -1359,16 +1359,16 @@ async fn berry_crlf_takeovers_round_trip_both_directions() { // The vendored `resolutions` entry is gone and the hosted pin (#404 // option C) took its place, in the manifest's own layout: BOM + CRLF. let hosted_pkg = std::fs::read_to_string(root.join("package.json")).unwrap(); - assert!( - hosted_pkg.starts_with('\u{feff}'), - "BOM kept: {hosted_pkg:?}" - ); + assert!(hosted_pkg.starts_with('\u{feff}'), "BOM kept: {hosted_pkg:?}"); let pin_line = format!(" \"left-pad@npm:1.3.0\": \"{hosted_url}\"\r\n"); assert!( hosted_pkg.contains(&pin_line) && !hosted_pkg.contains(".socket/vendor/"), "the hosted pin replaced the vendored resolutions entry: {hosted_pkg:?}" ); - let unpinned = hosted_pkg.replace(&format!(",\r\n \"resolutions\": {{\r\n{pin_line} }}"), ""); + let unpinned = hosted_pkg.replace( + &format!(",\r\n \"resolutions\": {{\r\n{pin_line} }}"), + "", + ); assert_eq!(unpinned, pkg, "nothing else in package.json changed"); let hosted_lock = std::fs::read_to_string(root.join("yarn.lock")).unwrap(); assert!( 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 5c0b94c1a..dfdee4f52 100644 --- a/crates/socket-patch-core/src/crawlers/python_crawler.rs +++ b/crates/socket-patch-core/src/crawlers/python_crawler.rs @@ -365,8 +365,8 @@ async fn find_local_venv_site_packages_with( // it once `poetry env use` recorded an env for the project (see // [`poetry_active_prefix`]). PDM likewise skips an activated venv under // `PDM_IGNORE_ACTIVE_VENV`. - let pdm_ignores_active = - pdm_env_flag(var, "PDM_IGNORE_ACTIVE_VENV") && pdm_drives_project(cwd).await; + let pdm_ignores_active = pdm_env_flag(var, "PDM_IGNORE_ACTIVE_VENV") + && pdm_drives_project(cwd).await; let active_prefix = match &poetry { Some(project) => poetry_active_prefix(project, var), None if pdm_ignores_active => None, @@ -540,7 +540,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() } @@ -2811,11 +2813,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/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 f0a16029f..3c345cda8 100644 --- a/crates/socket-patch-core/src/formats/gem/mod.rs +++ b/crates/socket-patch-core/src/formats/gem/mod.rs @@ -27,6 +27,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`). @@ -207,6 +208,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 3091134d2..04d5e6312 100644 --- a/crates/socket-patch-core/src/formats/registry.rs +++ b/crates/socket-patch-core/src/formats/registry.rs @@ -67,11 +67,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", @@ -122,11 +118,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), @@ -221,11 +213,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 64dbd6d9a..c7f51d5b2 100644 --- a/crates/socket-patch-core/src/formats/yarn/mod.rs +++ b/crates/socket-patch-core/src/formats/yarn/mod.rs @@ -62,10 +62,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 9fc5ebfd9..6e5b36e09 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 b649621c1..e11aa036d 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); @@ -876,11 +859,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() @@ -1132,10 +1112,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 cc4ea8c41..84e0dd8d7 100644 --- a/crates/socket-patch-core/src/hosted/memory/roots.rs +++ b/crates/socket-patch-core/src/hosted/memory/roots.rs @@ -40,23 +40,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(); @@ -217,15 +211,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 ec2fb15ee..f257d0bef 100644 --- a/crates/socket-patch-core/src/lib.rs +++ b/crates/socket-patch-core/src/lib.rs @@ -15,6 +15,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/group_equivalence_tests.rs b/crates/socket-patch-core/src/patch/redirect/group_equivalence_tests.rs index 480e315e9..10fe9d510 100644 --- a/crates/socket-patch-core/src/patch/redirect/group_equivalence_tests.rs +++ b/crates/socket-patch-core/src/patch/redirect/group_equivalence_tests.rs @@ -131,12 +131,11 @@ fn assert_same_with_metadata( bun_lockb_present, python_metadata, ); - let merged = merge_group_outputs(&prefix, run_groups_concurrently(&prefix, &groups)).map( - |mut merged| { + let merged = merge_group_outputs(&prefix, run_groups_concurrently(&prefix, &groups)) + .map(|mut merged| { merged.vlt_drives = vlt::vlt_drives(files, bun_lockb_present); merged - }, - ); + }); assert_eq!( merged.as_ref(), Some(&want), diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index fd821e2a5..d14775d81 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -47,18 +47,17 @@ 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::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; -pub(crate) use crate::formats::yarn::is_berry_lock; -pub(crate) mod hosted_url; #[cfg(test)] mod pnpm_equivalence_tests; mod poetry; @@ -67,17 +66,18 @@ mod python_lock_equivalence_tests; mod requirements; 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)] @@ -3826,12 +3826,7 @@ fn rewrite_yarn_berry( 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())), @@ -4016,10 +4011,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)); @@ -4197,11 +4189,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; } @@ -4226,6 +4214,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}, @@ -4796,6 +4785,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 @@ -8225,11 +8215,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()), @@ -8262,14 +8248,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"], @@ -8281,17 +8261,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 ); } @@ -8499,7 +8473,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![ @@ -8543,11 +8520,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)); @@ -8560,10 +8533,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 @@ -8810,9 +8780,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}"); @@ -8838,17 +8806,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}" ); @@ -8873,20 +8834,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")); @@ -8894,10 +8847,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"] ); @@ -8908,17 +8858,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 @@ -8943,10 +8886,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}" ); @@ -12018,11 +11958,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, @@ -12035,11 +11971,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 @@ -12054,10 +11986,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 ); @@ -12425,19 +12354,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), @@ -13864,10 +13785,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(); @@ -13883,10 +13801,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"); } } @@ -16166,8 +16081,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) = ( @@ -16177,8 +16091,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}'), ), 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 608dbfd74..95d910f23 100644 --- a/crates/socket-patch-core/src/patch/redirect/pdm.rs +++ b/crates/socket-patch-core/src/patch/redirect/pdm.rs @@ -235,18 +235,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( @@ -419,11 +410,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 c1376d4bb..87bd8ad5c 100644 --- a/crates/socket-patch-core/src/patch/redirect/pipenv.rs +++ b/crates/socket-patch-core/src/patch/redirect/pipenv.rs @@ -459,10 +459,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(); @@ -502,30 +499,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")); @@ -583,10 +570,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(); @@ -612,10 +596,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(); @@ -636,10 +617,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. @@ -650,6 +628,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 @@ -723,9 +702,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 b84dde5fa..624b74c4a 100644 --- a/crates/socket-patch-core/src/patch/redirect/poetry.rs +++ b/crates/socket-patch-core/src/patch/redirect/poetry.rs @@ -92,9 +92,7 @@ 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}"), @@ -102,9 +100,7 @@ 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) = @@ -140,18 +136,14 @@ 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}"), @@ -276,11 +268,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 f88aeb347..ea0fe324f 100644 --- a/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/upstream/mod.rs @@ -348,7 +348,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 { @@ -676,7 +678,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/patch/redirect/vlt.rs b/crates/socket-patch-core/src/patch/redirect/vlt.rs index c3561b44a..149868520 100644 --- a/crates/socket-patch-core/src/patch/redirect/vlt.rs +++ b/crates/socket-patch-core/src/patch/redirect/vlt.rs @@ -985,4 +985,5 @@ mod tests { ); assert_eq!(carried_pin_original(&relocked, &old), None); } + } diff --git a/crates/socket-patch-core/src/policy/mod.rs b/crates/socket-patch-core/src/policy/mod.rs index 605dfd9ce..15778f264 100644 --- a/crates/socket-patch-core/src/policy/mod.rs +++ b/crates/socket-patch-core/src/policy/mod.rs @@ -456,8 +456,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 } @@ -806,9 +805,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 2c18534f4..5e03be9d7 100644 --- a/crates/socket-patch-core/src/policy/tests.rs +++ b/crates/socket-patch-core/src/policy/tests.rs @@ -417,14 +417,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] @@ -582,10 +576,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"); } @@ -606,21 +597,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 be1476b19..f176426ce 100644 --- a/crates/socket-patch-core/src/update/download.rs +++ b/crates/socket-patch-core/src/update/download.rs @@ -741,10 +741,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 @@ -760,7 +757,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) @@ -826,10 +824,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 973c02877..e8f2284fe 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); @@ -1608,10 +1608,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")) @@ -1620,10 +1617,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(); @@ -1635,10 +1629,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/hatch.rs b/crates/socket-patch-core/src/utils/hatch.rs index 569727aed..4be79627a 100644 --- a/crates/socket-patch-core/src/utils/hatch.rs +++ b/crates/socket-patch-core/src/utils/hatch.rs @@ -265,7 +265,8 @@ fn rewrite_environments( .is_some_and(|kind| kind != "virtual") { return Err( - "Hatch sources, overrides and custom environments require agent mode".into(), + "Hatch sources, overrides and custom environments require agent mode" + .into(), ); } for key in ["dependencies", "extra-dependencies"] { 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/mod.rs b/crates/socket-patch-core/src/utils/mod.rs index e792f2f96..e3ade4d63 100644 --- a/crates/socket-patch-core/src/utils/mod.rs +++ b/crates/socket-patch-core/src/utils/mod.rs @@ -7,9 +7,9 @@ pub mod env_compat; pub mod failpoint; pub mod fs; pub mod group_commit; +pub mod notice; pub(crate) mod http; pub(crate) mod line_endings; -pub mod notice; pub mod pdm_lock; pub(crate) mod pep440; pub mod pipenv; diff --git a/crates/socket-patch-core/src/utils/process.rs b/crates/socket-patch-core/src/utils/process.rs index a8eb22cd7..13038c679 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 5eb997005..2ca51e107 100644 --- a/crates/socket-patch-core/src/utils/python_script.rs +++ b/crates/socket-patch-core/src/utils/python_script.rs @@ -627,12 +627,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_lock_text.rs b/crates/socket-patch-core/src/vendor/bun_lock_text.rs index cbdcc8d93..5a21c5bb4 100644 --- a/crates/socket-patch-core/src/vendor/bun_lock_text.rs +++ b/crates/socket-patch-core/src/vendor/bun_lock_text.rs @@ -557,4 +557,5 @@ mod tests { ); } } + } diff --git a/crates/socket-patch-core/src/vendor/bun_lockb.rs b/crates/socket-patch-core/src/vendor/bun_lockb.rs index 80c5a9617..7716a3b32 100644 --- a/crates/socket-patch-core/src/vendor/bun_lockb.rs +++ b/crates/socket-patch-core/src/vendor/bun_lockb.rs @@ -1867,10 +1867,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 @@ -1913,9 +1910,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/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/vlt.rs b/crates/socket-patch-core/src/vendor/lock_inventory/vlt.rs index ba1324448..f84eed675 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/vlt.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/vlt.rs @@ -6,8 +6,8 @@ use std::path::Path; use serde_json::{Map, Value}; -use super::view::ProjectView; use crate::constants::npm_family::VLT_LOCK; +use super::view::ProjectView; use crate::vendor::vlt_lock_text::{ is_default_registry, sniff_lock, split_dep_id, DepId, DepIdKind, LockSniff, }; 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 b4cf64fb6..b3149ccaa 100644 --- a/crates/socket-patch-core/src/vendor/prestage.rs +++ b/crates/socket-patch-core/src/vendor/prestage.rs @@ -464,10 +464,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/pypi.rs b/crates/socket-patch-core/src/vendor/pypi.rs index 1f57f74e2..74883c23e 100644 --- a/crates/socket-patch-core/src/vendor/pypi.rs +++ b/crates/socket-patch-core/src/vendor/pypi.rs @@ -3492,13 +3492,8 @@ wheels = [ tokio::fs::remove_dir_all(&uuid_dir).await.unwrap(); let bytes = served_wheel(b"service wheel at another filename"); let server = wiremock::MockServer::start().await; - mount_pypi_granted( - &server, - "six-1.16.0-py3-none-any.whl", - &sri_sha512(&bytes), - &bytes, - ) - .await; + mount_pypi_granted(&server, "six-1.16.0-py3-none-any.whl", &sri_sha512(&bytes), &bytes) + .await; let cfg = pypi_service_cfg(&server.uri(), VendorSource::Service, false); let error = crate::vendor::test_support::expect_failure(vendor(Some(cfg)).await); assert!( 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/gem.rs b/crates/socket-patch-core/src/vex/discover/gem.rs index 3e0718864..77f7388a8 100644 --- a/crates/socket-patch-core/src/vex/discover/gem.rs +++ b/crates/socket-patch-core/src/vex/discover/gem.rs @@ -125,10 +125,10 @@ use super::{ names_vendor_dir, simple_purl, vendor_ref, vendored_leaf_purl, DiscoverCtx, Discovery, PatchedRef, DIAG_LOCKFILE_UNPARSEABLE, DIAG_REF_INVALID, DIAG_REF_UNATTRIBUTABLE, }; +use crate::vendor::gem::{gem_declaration_any, quoted_literal}; use crate::formats::gem::{ bundler_manifest_for, same_remote, GemfileLock, Section, SpecLine, BUNDLER_LOCKS, }; -use crate::vendor::gem::{gem_declaration_any, quoted_literal}; pub(crate) async fn extract(ctx: &DiscoverCtx<'_>, out: &mut Discovery) { // Both locks, legacy spelling first (order only affects diagnostics). diff --git a/crates/socket-patch-core/src/vex/discover/maven.rs b/crates/socket-patch-core/src/vex/discover/maven.rs index fc9e1fdb0..734f3e61d 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 de8dc2e67..1e0bc6149 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 a8ed7092f..231d221ba 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. @@ -1926,11 +1709,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\ @@ -1958,13 +1737,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( @@ -1972,11 +1746,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}"); } @@ -2002,10 +1774,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}"); @@ -2043,12 +1812,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; @@ -2102,10 +1866,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(); @@ -2150,10 +1911,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}"); } @@ -2174,9 +1932,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 })), @@ -2246,17 +2002,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")); } 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};