use std::path::{Path, PathBuf};
use std::process::Command;
fn binary() -> PathBuf {
env!("CARGO_BIN_EXE_socket-patch").into()
}
fn write_manifest(root: &Path, purl: &str) {
let socket = root.join(".socket");
std::fs::create_dir_all(&socket).unwrap();
std::fs::write(
socket.join("manifest.json"),
format!(
r#"{{
"patches": {{
"{purl}": {{
"uuid": "11111111-1111-4111-8111-111111111111",
"exportedAt": "2024-01-01T00:00:00Z",
"files": {{}},
"vulnerabilities": {{}},
"description": "global-test",
"license": "MIT",
"tier": "free"
}}
}}
}}"#
),
)
.unwrap();
}
#[test]
fn apply_global_resolves_real_npm_prefix() {
let tmp = tempfile::tempdir().unwrap();
write_manifest(&tmp.path(), "pkg:npm/__global_test__@1.0.0");
let out = Command::new(binary())
.args(["apply", "--global", "--offline", "--json", "--silent"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
assert!(
code == 0 || code == 1,
"apply --global must not crash; got {code}; stdout={stdout}"
);
let _: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("apply --global must emit valid JSON");
}
#[test]
fn rollback_global_resolves_real_npm_prefix() {
let tmp = tempfile::tempdir().unwrap();
write_manifest(&tmp.path(), "pkg:npm/__rollback_global__@1.0.0");
let out = Command::new(binary())
.args([
"rollback",
"--global",
"--offline",
"--json",
"--silent",
])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
assert!(
code == 0 || code == 1,
"rollback --global must not crash; got {code}; stdout={stdout}"
);
}
#[test]
fn apply_global_prefix_uses_explicit_path() {
let tmp = tempfile::tempdir().unwrap();
let global_dir = tmp.path().join("global");
std::fs::create_dir_all(global_dir.join("node_modules")).unwrap();
write_manifest(tmp.path(), "pkg:npm/__explicit_prefix__@1.0.0");
let out = Command::new(binary())
.args([
"apply",
"--global",
"--global-prefix",
global_dir.to_str().unwrap(),
"--offline",
"--json",
"--silent",
])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
assert!(
code == 0 || code == 1,
"apply --global-prefix must not crash; stdout={stdout}"
);
}
#[test]
fn rollback_global_prefix_uses_explicit_path() {
let tmp = tempfile::tempdir().unwrap();
let global_dir = tmp.path().join("global");
std::fs::create_dir_all(global_dir.join("node_modules")).unwrap();
write_manifest(tmp.path(), "pkg:npm/__explicit_prefix__@1.0.0");
let out = Command::new(binary())
.args([
"rollback",
"--global",
"--global-prefix",
global_dir.to_str().unwrap(),
"--offline",
"--json",
"--silent",
])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
assert!(
code == 0 || code == 1,
"rollback --global-prefix must not crash"
);
}
#[test]
fn apply_global_with_empty_path_handles_missing_npm() {
let tmp = tempfile::tempdir().unwrap();
write_manifest(&tmp.path(), "pkg:npm/__missing_npm__@1.0.0");
let out = Command::new(binary())
.args(["apply", "--global", "--offline", "--json", "--silent"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.env("PATH", "/nonexistent-dir-for-test")
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
assert!(
code == 0 || code == 1,
"missing npm must not crash apply; got {code}; stdout={stdout}"
);
let _: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("envelope JSON must parse");
}
#[test]
fn rollback_global_with_empty_path_handles_missing_npm() {
let tmp = tempfile::tempdir().unwrap();
write_manifest(&tmp.path(), "pkg:npm/__missing_npm__@1.0.0");
let out = Command::new(binary())
.args([
"rollback",
"--global",
"--offline",
"--json",
"--silent",
])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.env("PATH", "/nonexistent-dir-for-test")
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
assert!(
code == 0 || code == 1,
"missing npm must not crash rollback; got {code}"
);
}
#[cfg(unix)]
fn write_stub(dir: &Path, name: &str, body: &str) {
use std::os::unix::fs::PermissionsExt;
let path = dir.join(name);
std::fs::write(&path, body).unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).unwrap();
}
#[cfg(unix)]
#[test]
fn apply_global_with_stub_npm_root_resolves_path() {
let tmp = tempfile::tempdir().unwrap();
let stub_dir = tmp.path().join("bin");
std::fs::create_dir_all(&stub_dir).unwrap();
let fake_global = tmp.path().join("fake-global/node_modules");
std::fs::create_dir_all(&fake_global).unwrap();
let stub_script = format!(
"#!/bin/sh\nif [ \"$1\" = \"root\" ] && [ \"$2\" = \"-g\" ]; then echo \"{}\"; exit 0; fi\nexit 0\n",
fake_global.display()
);
write_stub(&stub_dir, "npm", &stub_script);
write_manifest(tmp.path(), "pkg:npm/__stubbed_npm__@1.0.0");
let out = Command::new(binary())
.args(["apply", "--global", "--offline", "--json", "--silent"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.env("PATH", stub_dir.to_str().unwrap())
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
assert!(
code == 0 || code == 1,
"stubbed npm root must not crash; got {code}; stdout={stdout}"
);
}
#[cfg(unix)]
#[test]
fn apply_global_with_empty_npm_root_output_handles_error() {
let tmp = tempfile::tempdir().unwrap();
let stub_dir = tmp.path().join("bin");
std::fs::create_dir_all(&stub_dir).unwrap();
write_stub(&stub_dir, "npm", "#!/bin/sh\nexit 0\n");
write_manifest(tmp.path(), "pkg:npm/__empty_npm__@1.0.0");
let out = Command::new(binary())
.args(["apply", "--global", "--offline", "--json", "--silent"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.env("PATH", stub_dir.to_str().unwrap())
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
assert!(
code == 0 || code == 1,
"empty npm output must not crash; got {code}"
);
}
#[cfg(unix)]
#[test]
fn apply_global_with_failing_npm_handles_error() {
let tmp = tempfile::tempdir().unwrap();
let stub_dir = tmp.path().join("bin");
std::fs::create_dir_all(&stub_dir).unwrap();
write_stub(&stub_dir, "npm", "#!/bin/sh\nexit 1\n");
write_manifest(tmp.path(), "pkg:npm/__failing_npm__@1.0.0");
let out = Command::new(binary())
.args(["apply", "--global", "--offline", "--json", "--silent"])
.current_dir(tmp.path())
.env_remove("SOCKET_API_TOKEN")
.env("PATH", stub_dir.to_str().unwrap())
.output()
.expect("run socket-patch");
let code = out.status.code().unwrap_or(-1);
assert!(
code == 0 || code == 1,
"failing npm must not crash; got {code}"
);
}