diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 193dce8..a405ba0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,31 @@ permissions: contents: read jobs: + clippy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable + with: + toolchain: stable + components: clippy + + - name: Cache cargo + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ubuntu-latest-cargo-clippy-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ubuntu-latest-cargo-clippy- + + - name: Run clippy + run: cargo clippy --workspace --all-features -- -D warnings + test: strategy: matrix: @@ -22,7 +47,6 @@ jobs: uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable with: toolchain: stable - components: clippy - name: Cache cargo uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 @@ -34,9 +58,6 @@ jobs: key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ matrix.os }}-cargo- - - name: Run clippy - run: cargo clippy --workspace --all-features -- -D warnings - - name: Run tests run: cargo test --workspace --all-features @@ -120,5 +141,12 @@ jobs: with: python-version: "3.12" + - name: Setup Ruby + if: matrix.suite == 'e2e_gem' + uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1 + with: + ruby-version: '3.2' + bundler-cache: false + - name: Run e2e tests run: cargo test -p socket-patch-cli --all-features --test ${{ matrix.suite }} -- --ignored diff --git a/Cargo.lock b/Cargo.lock index e89f326..a94ce29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1218,7 +1218,7 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket-patch-cli" -version = "1.7.1" +version = "2.1.0" dependencies = [ "clap", "dialoguer", @@ -1236,7 +1236,7 @@ dependencies = [ [[package]] name = "socket-patch-core" -version = "1.7.1" +version = "2.1.0" dependencies = [ "hex", "once_cell", diff --git a/README.md b/README.md index e91d181..2243335 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ cargo install socket-patch-cli By default this builds with npm and PyPI support. For additional ecosystems: ```bash -cargo install socket-patch-cli --features cargo,golang,maven,gem,composer,nuget +cargo install socket-patch-cli --features cargo,golang,maven,composer,nuget ``` ## Quick Start diff --git a/crates/socket-patch-cli/Cargo.toml b/crates/socket-patch-cli/Cargo.toml index ffa41ed..8911c79 100644 --- a/crates/socket-patch-cli/Cargo.toml +++ b/crates/socket-patch-cli/Cargo.toml @@ -26,7 +26,6 @@ tempfile = { workspace = true } [features] default = [] cargo = ["socket-patch-core/cargo"] -gem = ["socket-patch-core/gem"] golang = ["socket-patch-core/golang"] maven = ["socket-patch-core/maven"] composer = ["socket-patch-core/composer"] diff --git a/crates/socket-patch-cli/src/ecosystem_dispatch.rs b/crates/socket-patch-cli/src/ecosystem_dispatch.rs index 1764efe..66bf254 100644 --- a/crates/socket-patch-cli/src/ecosystem_dispatch.rs +++ b/crates/socket-patch-cli/src/ecosystem_dispatch.rs @@ -7,7 +7,6 @@ use std::path::PathBuf; #[cfg(feature = "cargo")] use socket_patch_core::crawlers::CargoCrawler; -#[cfg(feature = "gem")] use socket_patch_core::crawlers::RubyCrawler; #[cfg(feature = "golang")] use socket_patch_core::crawlers::GoCrawler; @@ -141,7 +140,6 @@ pub async fn find_packages_for_purls( } // gem - #[cfg(feature = "gem")] if let Some(gem_purls) = partitioned.get(&Ecosystem::Gem) { if !gem_purls.is_empty() { let ruby_crawler = RubyCrawler; @@ -323,7 +321,6 @@ pub async fn crawl_all_ecosystems( all_packages.extend(cargo_packages); } - #[cfg(feature = "gem")] { let ruby_crawler = RubyCrawler; let gem_packages = ruby_crawler.crawl_all(options).await; @@ -468,7 +465,6 @@ pub async fn find_packages_for_rollback( } // gem - #[cfg(feature = "gem")] if let Some(gem_purls) = partitioned.get(&Ecosystem::Gem) { if !gem_purls.is_empty() { let ruby_crawler = RubyCrawler; diff --git a/crates/socket-patch-cli/tests/e2e_gem.rs b/crates/socket-patch-cli/tests/e2e_gem.rs index 8937174..e46fb9d 100644 --- a/crates/socket-patch-cli/tests/e2e_gem.rs +++ b/crates/socket-patch-cli/tests/e2e_gem.rs @@ -1,18 +1,35 @@ -#![cfg(feature = "gem")] -//! End-to-end tests for the RubyGems package patching lifecycle. +//! End-to-end tests for the RubyGems patch lifecycle. //! -//! These tests exercise crawling against a temporary directory with fake +//! Non-ignored tests exercise crawling against a temporary directory with fake //! gem layouts. They do **not** require network access or a real Ruby //! installation. //! +//! Ignored tests exercise the full CLI against the real Socket API, using the +//! **activestorage@5.2.0** patch (UUID `4bf7fe0b-dc57-4ea8-945f-bc4a04c47a15`), +//! which fixes CVE-2022-21831 (code injection). +//! //! # Running //! ```sh -//! cargo test -p socket-patch-cli --features gem --test e2e_gem +//! # Scan tests (no network needed) +//! cargo test -p socket-patch-cli --test e2e_gem +//! +//! # Full lifecycle (needs bundler + network) +//! cargo test -p socket-patch-cli --test e2e_gem -- --ignored //! ``` -use std::path::PathBuf; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; use std::process::{Command, Output}; +use sha2::{Digest, Sha256}; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const GEM_UUID: &str = "4bf7fe0b-dc57-4ea8-945f-bc4a04c47a15"; +const GEM_PURL: &str = "pkg:gem/activestorage@5.2.0"; + // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- @@ -21,16 +38,175 @@ fn binary() -> PathBuf { env!("CARGO_BIN_EXE_socket-patch").into() } -fn run(args: &[&str], cwd: &std::path::Path) -> Output { - Command::new(binary()) +fn has_command(cmd: &str) -> bool { + Command::new(cmd) + .arg("--version") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .is_ok() +} + +/// Compute Git SHA-256: `SHA256("blob \0" ++ content)`. +fn git_sha256(content: &[u8]) -> String { + let header = format!("blob {}\0", content.len()); + let mut hasher = Sha256::new(); + hasher.update(header.as_bytes()); + hasher.update(content); + hex::encode(hasher.finalize()) +} + +fn git_sha256_file(path: &Path) -> String { + let content = std::fs::read(path).unwrap_or_else(|e| panic!("read {}: {e}", path.display())); + git_sha256(&content) +} + +fn run(cwd: &Path, args: &[&str]) -> (i32, String, String) { + let out: Output = Command::new(binary()) + .args(args) + .current_dir(cwd) + .env_remove("SOCKET_API_TOKEN") + .output() + .expect("failed to execute socket-patch binary"); + + let code = out.status.code().unwrap_or(-1); + let stdout = String::from_utf8_lossy(&out.stdout).to_string(); + let stderr = String::from_utf8_lossy(&out.stderr).to_string(); + (code, stdout, stderr) +} + +fn assert_run_ok(cwd: &Path, args: &[&str], context: &str) -> (String, String) { + let (code, stdout, stderr) = run(cwd, args); + assert_eq!( + code, 0, + "{context} failed (exit {code}).\nstdout:\n{stdout}\nstderr:\n{stderr}" + ); + (stdout, stderr) +} + +fn bundle_run(cwd: &Path, args: &[&str]) { + let out = Command::new("bundle") .args(args) .current_dir(cwd) .output() - .expect("Failed to run socket-patch binary") + .expect("failed to run bundle"); + assert!( + out.status.success(), + "bundle {args:?} failed (exit {:?}).\nstdout:\n{}\nstderr:\n{}", + out.status.code(), + String::from_utf8_lossy(&out.stdout), + String::from_utf8_lossy(&out.stderr), + ); +} + +/// Write a minimal Gemfile that installs activestorage 5.2.0. +fn write_gemfile(cwd: &Path) { + std::fs::write( + cwd.join("Gemfile"), + "source 'https://rubygems.org'\ngem 'activestorage', '5.2.0'\n", + ) + .expect("write Gemfile"); +} + +/// Locate the gem install directory under vendor/bundle/ruby/*/gems/activestorage-5.2.0. +fn find_gem_dir(cwd: &Path) -> PathBuf { + let ruby_dir = cwd.join("vendor/bundle/ruby"); + for entry in std::fs::read_dir(&ruby_dir).expect("read vendor/bundle/ruby") { + let entry = entry.unwrap(); + let gem_dir = entry.path().join("gems").join("activestorage-5.2.0"); + if gem_dir.exists() { + return gem_dir; + } + } + panic!( + "could not find activestorage-5.2.0 gem dir under {}", + ruby_dir.display() + ); +} + +/// Read the manifest and return the files map for the gem patch. +fn read_patch_files(manifest_path: &Path) -> serde_json::Value { + let manifest: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(manifest_path).unwrap()).unwrap(); + let patch = &manifest["patches"][GEM_PURL]; + assert!(patch.is_object(), "manifest should contain {GEM_PURL}"); + patch["files"].clone() +} + +/// Record hashes of all files in the gem dir that will be patched. +fn record_original_hashes(gem_dir: &Path, files: &serde_json::Value) -> HashMap { + let mut hashes = HashMap::new(); + for (rel_path, _) in files.as_object().expect("files object") { + let full_path = gem_dir.join(rel_path); + let hash = if full_path.exists() { + git_sha256_file(&full_path) + } else { + String::new() + }; + hashes.insert(rel_path.clone(), hash); + } + hashes +} + +/// Verify all patched files match their afterHash from the manifest. +fn assert_after_hashes(gem_dir: &Path, files: &serde_json::Value) { + for (rel_path, info) in files.as_object().expect("files object") { + let after_hash = info["afterHash"] + .as_str() + .expect("afterHash should be a string"); + let full_path = gem_dir.join(rel_path); + assert!( + full_path.exists(), + "patched file should exist: {}", + full_path.display() + ); + assert_eq!( + git_sha256_file(&full_path), + after_hash, + "hash mismatch for {rel_path} after patching" + ); + } +} + +/// Verify all patched files match their beforeHash (or are removed if new). +fn assert_before_hashes(gem_dir: &Path, files: &serde_json::Value) { + for (rel_path, info) in files.as_object().expect("files object") { + let before_hash = info["beforeHash"].as_str().unwrap_or(""); + let full_path = gem_dir.join(rel_path); + if before_hash.is_empty() { + assert!( + !full_path.exists(), + "new file {rel_path} should be removed after rollback" + ); + } else { + assert_eq!( + git_sha256_file(&full_path), + before_hash, + "{rel_path} should match beforeHash" + ); + } + } +} + +/// Verify files match the originally recorded hashes. +fn assert_original_hashes(gem_dir: &Path, original_hashes: &HashMap) { + for (rel_path, orig_hash) in original_hashes { + if orig_hash.is_empty() { + continue; + } + let full_path = gem_dir.join(rel_path); + if full_path.exists() { + assert_eq!( + git_sha256_file(&full_path), + *orig_hash, + "{rel_path} should match original hash" + ); + } + } } // --------------------------------------------------------------------------- -// Tests +// Scan tests (no network needed) // --------------------------------------------------------------------------- /// Verify that `socket-patch scan` discovers gems in a vendor/bundle layout. @@ -59,10 +235,11 @@ fn scan_discovers_vendored_gems() { let nokogiri_dir = gems_dir.join("nokogiri-1.15.4"); std::fs::create_dir_all(nokogiri_dir.join("lib")).unwrap(); - let output = run( - &["scan", "--cwd", project_dir.to_str().unwrap()], - &project_dir, - ); + let output = Command::new(binary()) + .args(["scan", "--cwd", project_dir.to_str().unwrap()]) + .current_dir(&project_dir) + .output() + .expect("Failed to run socket-patch binary"); let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let combined = format!("{stdout}{stderr}"); @@ -96,10 +273,11 @@ fn scan_discovers_gems_with_gemspec() { std::fs::create_dir_all(&net_http_dir).unwrap(); std::fs::write(net_http_dir.join("net-http.gemspec"), "# gemspec\n").unwrap(); - let output = run( - &["scan", "--json", "--cwd", project_dir.to_str().unwrap()], - &project_dir, - ); + let output = Command::new(binary()) + .args(["scan", "--json", "--cwd", project_dir.to_str().unwrap()]) + .current_dir(&project_dir) + .output() + .expect("Failed to run socket-patch binary"); let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); let combined = format!("{stdout}{stderr}"); @@ -109,3 +287,162 @@ fn scan_discovers_gems_with_gemspec() { "Expected scan output, got:\n{combined}" ); } + +// --------------------------------------------------------------------------- +// Lifecycle tests (need bundler + network) +// --------------------------------------------------------------------------- + +/// Full lifecycle: get -> list (verify CVE-2022-21831) -> rollback -> apply -> remove. +#[test] +#[ignore] +fn test_gem_full_lifecycle() { + if !has_command("bundle") { + eprintln!("SKIP: bundle not found on PATH"); + return; + } + + let dir = tempfile::tempdir().unwrap(); + let cwd = dir.path(); + + // -- Setup: create project and install activestorage@5.2.0 ---------------- + write_gemfile(cwd); + bundle_run(cwd, &["install", "--path", "vendor/bundle"]); + + let gem_dir = find_gem_dir(cwd); + + // -- GET: download + apply patch ------------------------------------------ + assert_run_ok(cwd, &["get", GEM_UUID], "get"); + + let manifest_path = cwd.join(".socket/manifest.json"); + assert!(manifest_path.exists(), ".socket/manifest.json should exist after get"); + + let manifest: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&manifest_path).unwrap()).unwrap(); + let patch = &manifest["patches"][GEM_PURL]; + assert!(patch.is_object(), "manifest should contain {GEM_PURL}"); + assert_eq!(patch["uuid"].as_str().unwrap(), GEM_UUID); + + let files = &patch["files"]; + assert!( + files.as_object().map_or(false, |f| !f.is_empty()), + "patch should modify at least one file" + ); + + // Files should now be patched — verify against afterHash from manifest. + assert_after_hashes(&gem_dir, files); + + // -- LIST: verify JSON output --------------------------------------------- + let (stdout, _) = assert_run_ok(cwd, &["list", "--json"], "list --json"); + let list: serde_json::Value = serde_json::from_str(&stdout).unwrap(); + let patches = list["patches"].as_array().expect("patches should be an array"); + assert_eq!(patches.len(), 1); + assert_eq!(patches[0]["uuid"].as_str().unwrap(), GEM_UUID); + assert_eq!(patches[0]["purl"].as_str().unwrap(), GEM_PURL); + + let vulns = patches[0]["vulnerabilities"] + .as_array() + .expect("vulnerabilities array"); + assert!(!vulns.is_empty(), "patch should report at least one vulnerability"); + + let has_cve = vulns.iter().any(|v| { + v["cves"] + .as_array() + .map_or(false, |cves| cves.iter().any(|c| c == "CVE-2022-21831")) + }); + assert!(has_cve, "vulnerability list should include CVE-2022-21831"); + + // -- ROLLBACK: restore original files ------------------------------------- + assert_run_ok(cwd, &["rollback"], "rollback"); + assert_before_hashes(&gem_dir, files); + + // -- APPLY: re-apply from manifest ---------------------------------------- + assert_run_ok(cwd, &["apply"], "apply"); + assert_after_hashes(&gem_dir, files); + + // -- REMOVE: rollback + remove from manifest ------------------------------ + assert_run_ok(cwd, &["remove", GEM_UUID], "remove"); + assert_before_hashes(&gem_dir, files); + + let manifest: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&manifest_path).unwrap()).unwrap(); + assert!( + manifest["patches"].as_object().unwrap().is_empty(), + "manifest should be empty after remove" + ); +} + +/// `get --no-apply` + `apply --dry-run` should not modify files. +#[test] +#[ignore] +fn test_gem_dry_run() { + if !has_command("bundle") { + eprintln!("SKIP: bundle not found on PATH"); + return; + } + + let dir = tempfile::tempdir().unwrap(); + let cwd = dir.path(); + + write_gemfile(cwd); + bundle_run(cwd, &["install", "--path", "vendor/bundle"]); + + let gem_dir = find_gem_dir(cwd); + + // Download without applying. + assert_run_ok(cwd, &["get", GEM_UUID, "--no-apply"], "get --no-apply"); + + // Read manifest to get file list and expected hashes. + let manifest_path = cwd.join(".socket/manifest.json"); + let files = read_patch_files(&manifest_path); + let original_hashes = record_original_hashes(&gem_dir, &files); + + // Files should still be original (not patched). + assert_original_hashes(&gem_dir, &original_hashes); + + // Dry-run should succeed but leave files untouched. + assert_run_ok(cwd, &["apply", "--dry-run"], "apply --dry-run"); + assert_original_hashes(&gem_dir, &original_hashes); + + // Real apply should work. + assert_run_ok(cwd, &["apply"], "apply"); + assert_after_hashes(&gem_dir, &files); +} + +/// `get --save-only` should save the patch to the manifest without applying. +#[test] +#[ignore] +fn test_gem_save_only() { + if !has_command("bundle") { + eprintln!("SKIP: bundle not found on PATH"); + return; + } + + let dir = tempfile::tempdir().unwrap(); + let cwd = dir.path(); + + write_gemfile(cwd); + bundle_run(cwd, &["install", "--path", "vendor/bundle"]); + + let gem_dir = find_gem_dir(cwd); + + // Download with --save-only. + assert_run_ok(cwd, &["get", GEM_UUID, "--save-only"], "get --save-only"); + + // Read manifest to get file list and expected hashes. + let manifest_path = cwd.join(".socket/manifest.json"); + let files = read_patch_files(&manifest_path); + let original_hashes = record_original_hashes(&gem_dir, &files); + + // Files should still be original (not patched). + assert_original_hashes(&gem_dir, &original_hashes); + + let manifest: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&manifest_path).unwrap()).unwrap(); + let patch = &manifest["patches"][GEM_PURL]; + assert!(patch.is_object(), "manifest should contain {GEM_PURL}"); + assert_eq!(patch["uuid"].as_str().unwrap(), GEM_UUID); + + // Real apply should work. + assert_run_ok(cwd, &["apply"], "apply"); + assert_after_hashes(&gem_dir, &files); +} diff --git a/crates/socket-patch-core/Cargo.toml b/crates/socket-patch-core/Cargo.toml index 4150376..c081348 100644 --- a/crates/socket-patch-core/Cargo.toml +++ b/crates/socket-patch-core/Cargo.toml @@ -23,7 +23,6 @@ once_cell = { workspace = true } [features] default = [] cargo = [] -gem = [] golang = [] maven = [] composer = [] diff --git a/crates/socket-patch-core/README.md b/crates/socket-patch-core/README.md index ad92c8f..a365fb0 100644 --- a/crates/socket-patch-core/README.md +++ b/crates/socket-patch-core/README.md @@ -6,7 +6,7 @@ Core library for [socket-patch](https://github.com/SocketDev/socket-patch) — a - **Manifest management** — read, write, and validate `.socket/manifest.json` patch manifests - **Patch engine** — apply and rollback file-level patches using git SHA-256 content hashes -- **Crawlers** — discover installed packages across npm and PyPI (default), plus Cargo, Go, Maven, Ruby gems, Composer, and NuGet (via feature flags) +- **Crawlers** — discover installed packages across npm, PyPI, and Ruby gems (default), plus Cargo, Go, Maven, Composer, and NuGet (via feature flags) - **API client** — fetch patches from the Socket API - **Utilities** — PURL parsing, blob storage, hash verification, fuzzy matching diff --git a/crates/socket-patch-core/src/crawlers/mod.rs b/crates/socket-patch-core/src/crawlers/mod.rs index f698608..5ec0788 100644 --- a/crates/socket-patch-core/src/crawlers/mod.rs +++ b/crates/socket-patch-core/src/crawlers/mod.rs @@ -3,7 +3,6 @@ pub mod python_crawler; pub mod types; #[cfg(feature = "cargo")] pub mod cargo_crawler; -#[cfg(feature = "gem")] pub mod ruby_crawler; #[cfg(feature = "golang")] pub mod go_crawler; @@ -19,7 +18,6 @@ pub use python_crawler::PythonCrawler; pub use types::*; #[cfg(feature = "cargo")] pub use cargo_crawler::CargoCrawler; -#[cfg(feature = "gem")] pub use ruby_crawler::RubyCrawler; #[cfg(feature = "golang")] pub use go_crawler::GoCrawler; diff --git a/crates/socket-patch-core/src/crawlers/types.rs b/crates/socket-patch-core/src/crawlers/types.rs index 18f981a..9bcdbdd 100644 --- a/crates/socket-patch-core/src/crawlers/types.rs +++ b/crates/socket-patch-core/src/crawlers/types.rs @@ -7,7 +7,6 @@ pub enum Ecosystem { Pypi, #[cfg(feature = "cargo")] Cargo, - #[cfg(feature = "gem")] Gem, #[cfg(feature = "golang")] Golang, @@ -27,7 +26,6 @@ impl Ecosystem { Ecosystem::Pypi, #[cfg(feature = "cargo")] Ecosystem::Cargo, - #[cfg(feature = "gem")] Ecosystem::Gem, #[cfg(feature = "golang")] Ecosystem::Golang, @@ -46,7 +44,6 @@ impl Ecosystem { if purl.starts_with("pkg:cargo/") { return Some(Ecosystem::Cargo); } - #[cfg(feature = "gem")] if purl.starts_with("pkg:gem/") { return Some(Ecosystem::Gem); } @@ -82,7 +79,6 @@ impl Ecosystem { Ecosystem::Pypi => "pkg:pypi/", #[cfg(feature = "cargo")] Ecosystem::Cargo => "pkg:cargo/", - #[cfg(feature = "gem")] Ecosystem::Gem => "pkg:gem/", #[cfg(feature = "golang")] Ecosystem::Golang => "pkg:golang/", @@ -102,7 +98,6 @@ impl Ecosystem { Ecosystem::Pypi => "pypi", #[cfg(feature = "cargo")] Ecosystem::Cargo => "cargo", - #[cfg(feature = "gem")] Ecosystem::Gem => "gem", #[cfg(feature = "golang")] Ecosystem::Golang => "golang", @@ -122,7 +117,6 @@ impl Ecosystem { Ecosystem::Pypi => "python", #[cfg(feature = "cargo")] Ecosystem::Cargo => "cargo", - #[cfg(feature = "gem")] Ecosystem::Gem => "ruby", #[cfg(feature = "golang")] Ecosystem::Golang => "go", @@ -218,15 +212,11 @@ mod tests { fn test_all_count() { let all = Ecosystem::all(); #[allow(unused_mut)] - let mut expected = 2; + let mut expected = 3; #[cfg(feature = "cargo")] { expected += 1; } - #[cfg(feature = "gem")] - { - expected += 1; - } #[cfg(feature = "golang")] { expected += 1; @@ -272,7 +262,6 @@ mod tests { assert_eq!(Ecosystem::Cargo.purl_prefix(), "pkg:cargo/"); } - #[cfg(feature = "gem")] #[test] fn test_from_purl_gem() { assert_eq!( @@ -281,7 +270,6 @@ mod tests { ); } - #[cfg(feature = "gem")] #[test] fn test_gem_properties() { assert_eq!(Ecosystem::Gem.cli_name(), "gem"); diff --git a/crates/socket-patch-core/src/package_json/update.rs b/crates/socket-patch-core/src/package_json/update.rs index 4c7486a..d5f1742 100644 --- a/crates/socket-patch-core/src/package_json/update.rs +++ b/crates/socket-patch-core/src/package_json/update.rs @@ -156,7 +156,7 @@ mod tests { let result = update_package_json(&pkg, false).await; assert_eq!(result.status, UpdateStatus::Updated); let content = fs::read_to_string(&pkg).await.unwrap(); - assert!(content.contains("socket patch apply")); + assert!(content.contains("socket-patch apply")); } #[tokio::test] @@ -178,7 +178,7 @@ mod tests { assert_eq!(result.status, UpdateStatus::Updated); let content = fs::read_to_string(&pkg).await.unwrap(); assert!(content.contains("postinstall")); - assert!(content.contains("socket patch apply")); + assert!(content.contains("socket-patch apply")); } #[tokio::test] diff --git a/crates/socket-patch-core/src/utils/purl.rs b/crates/socket-patch-core/src/utils/purl.rs index 546da31..0699eb6 100644 --- a/crates/socket-patch-core/src/utils/purl.rs +++ b/crates/socket-patch-core/src/utils/purl.rs @@ -65,7 +65,6 @@ pub fn parse_npm_purl(purl: &str) -> Option<(Option<&str>, &str, &str)> { } /// Check if a PURL is a Ruby gem. -#[cfg(feature = "gem")] pub fn is_gem_purl(purl: &str) -> bool { purl.starts_with("pkg:gem/") } @@ -73,7 +72,6 @@ pub fn is_gem_purl(purl: &str) -> bool { /// Parse a gem PURL to extract name and version. /// /// e.g., `"pkg:gem/rails@7.1.0"` -> `Some(("rails", "7.1.0"))` -#[cfg(feature = "gem")] pub fn parse_gem_purl(purl: &str) -> Option<(&str, &str)> { let base = strip_purl_qualifiers(purl); let rest = base.strip_prefix("pkg:gem/")?; @@ -87,7 +85,6 @@ pub fn parse_gem_purl(purl: &str) -> Option<(&str, &str)> { } /// Build a gem PURL from components. -#[cfg(feature = "gem")] pub fn build_gem_purl(name: &str, version: &str) -> String { format!("pkg:gem/{name}@{version}") } @@ -296,7 +293,6 @@ pub fn parse_purl(purl: &str) -> Option<(&str, String, &str)> { } return Some(("golang", module_path.to_string(), version)); } - #[cfg(feature = "gem")] if let Some(rest) = base.strip_prefix("pkg:gem/") { let at_idx = rest.rfind('@')?; let name = &rest[..at_idx]; @@ -511,7 +507,6 @@ mod tests { assert_eq!(ver, "1.0.200"); } - #[cfg(feature = "gem")] #[test] fn test_is_gem_purl() { assert!(is_gem_purl("pkg:gem/rails@7.1.0")); @@ -519,7 +514,6 @@ mod tests { assert!(!is_gem_purl("pkg:pypi/requests@2.28.0")); } - #[cfg(feature = "gem")] #[test] fn test_parse_gem_purl() { assert_eq!( @@ -535,7 +529,6 @@ mod tests { assert_eq!(parse_gem_purl("pkg:gem/rails@"), None); } - #[cfg(feature = "gem")] #[test] fn test_build_gem_purl() { assert_eq!( @@ -544,7 +537,6 @@ mod tests { ); } - #[cfg(feature = "gem")] #[test] fn test_gem_purl_round_trip() { let purl = build_gem_purl("nokogiri", "1.16.5"); @@ -553,7 +545,6 @@ mod tests { assert_eq!(version, "1.16.5"); } - #[cfg(feature = "gem")] #[test] fn test_parse_purl_gem() { let (eco, dir, ver) = parse_purl("pkg:gem/rails@7.1.0").unwrap();