Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 30 additions & 40 deletions crates/socket-patch-cli/tests/apply/apply_network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
//! node_modules and leaves `.socket/` byte-identical.

use std::path::Path;
use std::process::Command;

use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

use crate::common::{binary, git_sha256};
use crate::common::{binary, git_sha256, hermetic};

const ORG_SLUG: &str = "test-org";

Expand Down Expand Up @@ -79,7 +78,7 @@ fn run_apply(cwd: &Path, api_url: &str, extra: &[&str]) -> (i32, String, String)
// --api-url/--api-token/--org flags).
let mut argv: Vec<&str> = vec!["apply", "--json"];
argv.extend_from_slice(extra);
let out = Command::new(binary())
let out = hermetic::command(&binary())
.args(&argv)
.current_dir(cwd)
.env("SOCKET_API_URL", api_url)
Expand Down Expand Up @@ -323,10 +322,9 @@ async fn apply_dry_run_emits_verified_event_without_writing() {
std::fs::write(blobs.join(&after_hash), after).unwrap();

// No mock needed — apply finds everything locally.
let out = Command::new(binary())
let out = hermetic::command(&binary())
.args(["apply", "--json", "--dry-run", "--offline"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
Expand Down Expand Up @@ -403,10 +401,9 @@ async fn apply_with_force_overrides_hash_mismatch() {

// Without --force apply should fail (hash mismatch). With --force it
// should bypass the verification and write the patched content.
let out = Command::new(binary())
let out = hermetic::command(&binary())
.args(["apply", "--json", "--offline", "--force"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
Expand Down Expand Up @@ -467,10 +464,9 @@ async fn apply_hash_mismatch_default_warns_and_applies_strict_fails() {
// DEFAULT: the mismatch is overwritten with the full verified patched
// content (the blob is hash-gated to afterHash) and surfaced as a warning event — exit 0.
let tmp = fixture();
let out = Command::new(binary())
let out = hermetic::command(&binary())
.args(["apply", "--json", "--offline"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.output()
.expect("run socket-patch");
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
Expand Down Expand Up @@ -500,10 +496,9 @@ async fn apply_hash_mismatch_default_warns_and_applies_strict_fails() {

// The human run logs the warning to stderr.
let tmp = fixture();
let out = Command::new(binary())
let out = hermetic::command(&binary())
.args(["apply", "--offline", "--yes"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.output()
.expect("run socket-patch");
let stderr = String::from_utf8_lossy(&out.stderr);
Expand All @@ -515,10 +510,9 @@ async fn apply_hash_mismatch_default_warns_and_applies_strict_fails() {

// --strict: fail closed — exit 1, failed event, file untouched.
let tmp = fixture();
let out = Command::new(binary())
let out = hermetic::command(&binary())
.args(["apply", "--json", "--offline", "--strict"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.output()
.expect("run socket-patch");
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
Expand Down Expand Up @@ -591,15 +585,13 @@ async fn apply_pypi_package_uses_python_crawler() {
// Run apply restricted to pypi. With the venv staged on disk and the
// after-blob pre-cached, this must locate the package via the python
// crawler and patch it — exercising the pypi dispatch branch end to
// end, not just "without panicking". `VIRTUAL_ENV` is cleared so an
// ambient venv in CI can't redirect discovery away from our `.venv`.
let out = Command::new(binary())
.args(["apply", "--json", "--offline", "--ecosystems", "pypi"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.env_remove("VIRTUAL_ENV")
.output()
.expect("run socket-patch");
// end, not just "without panicking". An ambient venv is scrubbed so CI
// can't redirect discovery away from our `.venv`.
let mut cmd = hermetic::command(&binary());
cmd.args(["apply", "--json", "--offline", "--ecosystems", "pypi"])
.current_dir(tmp.path());
hermetic::scrub_extra(&mut cmd, &[hermetic::Extra::Venv]);
let out = cmd.output().expect("run socket-patch");
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();
Expand Down Expand Up @@ -655,15 +647,14 @@ async fn apply_uses_locally_cached_blob_without_fetching() {

// No mock server. If apply tries to hit the network, the test will
// fail (connection refused) — proving the local-blob fast path is
// taken when sources are already on disk.
let out = Command::new(binary())
// taken when sources are already on disk. Both URLs are pinned: with
// no token the child takes the public-proxy path.
let out = hermetic::command(&binary())
.args(["apply", "--json"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.env(
"SOCKET_API_URL",
"http://127.0.0.1:1", // unreachable port — should never be contacted
)
// Unreachable port: should never be contacted.
.env("SOCKET_API_URL", "http://127.0.0.1:1")
.env("SOCKET_PROXY_URL", "http://127.0.0.1:1")
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
Expand Down Expand Up @@ -819,19 +810,18 @@ async fn offline_apply_with_token_makes_zero_network_requests() {
std::fs::create_dir_all(&blobs).expect("create blobs");
std::fs::write(blobs.join(&after_hash), after).expect("stage blob");

let mut cmd = Command::new(binary());
let mut cmd = hermetic::command(&binary());
cmd.args(["apply", "--json", "--offline"])
.current_dir(tmp.path());
// Scrub ambient SOCKET_* (an ambient SOCKET_ORG_SLUG would skip the
// auto-resolution and make this test vacuously green), then seed only
// what the scenario needs: token set, org absent, both URLs pinned to
// the mock so any traffic — API or proxy path — is captured.
for (k, _) in std::env::vars_os() {
if k.to_string_lossy().starts_with("SOCKET_") && k.to_string_lossy() != "SOCKET_NO_CONFIG" {
cmd.env_remove(&k);
}
}
cmd.env("SOCKET_API_URL", mock.uri())
// The builder scrubs ambient SOCKET_* (an ambient SOCKET_ORG_SLUG would
// skip the auto-resolution and make this test vacuously green) and
// keeps the config layer off; seed only what the scenario needs: token
// set, org absent, both URLs pinned to the mock so any traffic — API or
// proxy path — is captured. Telemetry is switched back on (the builder
// forces it off) so only `--offline` can keep the telemetry POST away
// from the mock.
cmd.env("SOCKET_TELEMETRY_DISABLED", "0")
.env("SOCKET_API_URL", mock.uri())
.env("SOCKET_PROXY_URL", mock.uri())
.env("SOCKET_API_TOKEN", "fake-token-for-test");
let out = cmd.output().expect("run socket-patch");
Expand Down
27 changes: 9 additions & 18 deletions crates/socket-patch-cli/tests/e2e_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

#[path = "common/mod.rs"]
mod common;
use common::{binary, git_sha256};
use common::{binary, git_sha256, hermetic};

use std::path::Path;
use std::process::{Command, Output};
Expand Down Expand Up @@ -93,24 +93,14 @@ fn git_sha256_file(path: &Path) -> String {
}

fn run(cwd: &Path, args: &[&str]) -> (i32, String, String) {
let mut cmd = Command::new(binary());
// The shared builder scrubs every ambient `SOCKET_*` (an ambient
// SOCKET_DRY_RUN would turn every `scan --mode agent` into a preview,
// SOCKET_GLOBAL would aim mutations at the host's real global
// node_modules) and forces the config layer, update check and telemetry
// off; removing SOCKET_API_TOKEN also forces the public proxy
// (free-tier).
let mut cmd = hermetic::command(&binary());
cmd.args(args).current_dir(cwd);
// The binary binds a wide `SOCKET_*` env surface (SOCKET_CWD,
// SOCKET_DRY_RUN, SOCKET_GLOBAL, SOCKET_GLOBAL_PREFIX, SOCKET_PROXY_URL,
// SOCKET_MANIFEST_PATH, ...). An ambient value silently changes what
// these tests exercise — SOCKET_DRY_RUN=true turns every `scan --mode agent`
// into a no-op preview, and SOCKET_GLOBAL aims mutations at the host's
// *real* global node_modules. Scrub the whole prefix so only the flags
// each test passes are in effect; removing SOCKET_API_TOKEN also forces
// the public proxy (free-tier). Telemetry opt-outs are deliberately kept
// so an opted-out dev stays opted out.
for (key, _) in std::env::vars_os() {
let name = key.to_string_lossy();
if name.starts_with("SOCKET_") && !name.contains("TELEMETRY") && name != "SOCKET_NO_CONFIG"
{
cmd.env_remove(&key);
}
}
let out: Output = cmd.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();
Expand All @@ -130,6 +120,7 @@ fn assert_run_ok(cwd: &Path, args: &[&str], context: &str) -> (String, String) {
fn npm_run(cwd: &Path, args: &[&str]) {
let mut cmd = Command::new("npm");
cmd.args(args).current_dir(cwd);
hermetic::scrub_socket_vars(&mut cmd);
cache_env::isolate(&mut cmd);
let out = cmd.output().expect("failed to run npm");
assert!(
Expand Down
18 changes: 8 additions & 10 deletions crates/socket-patch-cli/tests/e2e_socket_yml_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
//! scanned through the real binary in hosted, agent and vendored mode
//! against a mock patch API that serves a small catalog.

#[path = "common/hermetic.rs"]
mod hermetic;
#[path = "prebuilt_common/mod.rs"]
mod prebuilt_common;

Expand Down Expand Up @@ -403,18 +405,14 @@ impl Repo {
}
}

/// Run the binary with ambient `SOCKET_*` scrubbed; `(code, stdout, stderr)`.
/// Run the binary with the hermetic `SOCKET_*` environment (ambient values
/// scrubbed, config layer, update check and telemetry forced off);
/// `(code, stdout, stderr)`.
fn run_cli(cwd: &Path, args: &[&str], env: &[(&str, &str)]) -> (i32, String, String) {
let mut cmd = std::process::Command::new(env!("CARGO_BIN_EXE_socket-patch"));
let mut cmd = hermetic::binary_command();
cmd.args(args).current_dir(cwd);
for (key, _) in std::env::vars() {
if key.starts_with("SOCKET_") && key != "SOCKET_NO_CONFIG" {
cmd.env_remove(key);
}
}
cmd.env_remove("GIT_CEILING_DIRECTORIES")
.env_remove("VIRTUAL_ENV");
cmd.env("SOCKET_TELEMETRY_DISABLED", "1");
hermetic::scrub_extra(&mut cmd, &[hermetic::Extra::Venv, hermetic::Extra::Bundle]);
cmd.env_remove("GIT_CEILING_DIRECTORIES");
// The fixture's hosted pins name this origin; it makes them recorded.
cmd.env("SOCKET_PATCH_SERVER_URL", "http://patch.test");
let absent = cwd.join(".absent-npm-config");
Expand Down
3 changes: 0 additions & 3 deletions crates/socket-patch-cli/tests/spawn_env_hygiene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,11 @@ use serial_test::serial;
/// children see whatever `SOCKET_*` the ambient shell exports unless the file
/// scrubs by hand.
const PENDING_RAW_SPAWNS: &[&str] = &[
"apply/apply_network.rs",
"e2e_hosted_production.rs",
"e2e_maven.rs",
"e2e_npm.rs",
"e2e_redirect_bun_build.rs",
"e2e_redirect_cargo_shapes.rs",
"e2e_scan.rs",
"e2e_socket_yml_policy.rs",
"e2e_vendored_production.rs",
"e2e_vex_vendor.rs",
"rollback/rollback_multicopy_blob_gate.rs",
Expand Down
Loading