#![allow(dead_code)]
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use sha2::{Digest, Sha256};
pub fn binary() -> PathBuf {
env!("CARGO_BIN_EXE_socket-patch").into()
}
pub fn has_command(cmd: &str) -> bool {
Command::new(cmd)
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok()
}
pub fn run(cwd: &Path, args: &[&str]) -> (i32, String, String) {
run_with_env(cwd, args, &[])
}
pub fn run_with_env(
cwd: &Path,
args: &[&str],
env: &[(&str, &str)],
) -> (i32, String, String) {
let mut cmd = Command::new(binary());
cmd.args(args).current_dir(cwd).env_remove("SOCKET_API_TOKEN");
for (k, v) in env {
cmd.env(k, v);
}
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();
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
(code, stdout, stderr)
}
pub 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)
}
pub 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())
}
pub 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)
}
pub fn sha256_hex(content: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(content);
format!("{:x}", hasher.finalize())
}
pub fn npm_run(cwd: &Path, args: &[&str]) {
run_toolchain(cwd, "npm", args, &[]);
}
pub fn pnpm_run(cwd: &Path, args: &[&str], extra_env: &[(&str, &str)]) {
run_toolchain(cwd, "pnpm", args, extra_env);
}
pub fn cargo_run(cwd: &Path, args: &[&str], extra_env: &[(&str, &str)]) -> Output {
let mut cmd = Command::new("cargo");
cmd.args(args).current_dir(cwd);
for (k, v) in extra_env {
cmd.env(k, v);
}
cmd.output().expect("failed to run cargo")
}
fn run_toolchain(cwd: &Path, exe: &str, args: &[&str], extra_env: &[(&str, &str)]) {
let mut cmd = Command::new(exe);
cmd.args(args).current_dir(cwd);
for (k, v) in extra_env {
cmd.env(k, v);
}
let out = cmd
.output()
.unwrap_or_else(|e| panic!("failed to run {exe}: {e}"));
assert!(
out.status.success(),
"{exe} {args:?} failed (exit {:?}).\nstdout:\n{}\nstderr:\n{}",
out.status.code(),
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr),
);
}
pub fn write_package_json(cwd: &Path) {
std::fs::write(
cwd.join("package.json"),
r#"{"name":"e2e-test","version":"0.0.0","private":true}"#,
)
.expect("write package.json");
}
pub struct PatchEntry<'a> {
pub file_name: &'a str,
pub before_hash: &'a str,
pub after_hash: &'a str,
}
pub fn write_minimal_manifest(
socket_dir: &Path,
purl: &str,
uuid: &str,
files: &[PatchEntry<'_>],
) -> PathBuf {
std::fs::create_dir_all(socket_dir).expect("create .socket dir");
let mut files_map = serde_json::Map::new();
for f in files {
files_map.insert(
f.file_name.to_string(),
serde_json::json!({
"beforeHash": f.before_hash,
"afterHash": f.after_hash,
}),
);
}
let manifest = serde_json::json!({
"patches": {
purl: {
"uuid": uuid,
"exportedAt": "2026-01-01T00:00:00Z",
"files": files_map,
"vulnerabilities": {},
"description": "synthetic test patch",
"license": "MIT",
"tier": "free",
}
}
});
let path = socket_dir.join("manifest.json");
std::fs::write(&path, serde_json::to_string_pretty(&manifest).unwrap())
.expect("write manifest.json");
path
}
pub fn write_blob(socket_dir: &Path, hash: &str, content: &[u8]) {
let blobs = socket_dir.join("blobs");
std::fs::create_dir_all(&blobs).expect("create .socket/blobs");
std::fs::write(blobs.join(hash), content).expect("write blob");
}
pub fn parse_json_envelope(stdout: &str) -> serde_json::Value {
serde_json::from_str(stdout)
.unwrap_or_else(|e| panic!("failed to parse JSON envelope: {e}\nstdout:\n{stdout}"))
}
pub fn json_string<'a>(env: &'a serde_json::Value, key: &str) -> Option<&'a str> {
env.get(key).and_then(|v| v.as_str())
}
pub fn envelope_error_code(env: &serde_json::Value) -> Option<&str> {
env.get("error")?.get("code")?.as_str()
}
pub fn envelope_error_message(env: &serde_json::Value) -> Option<&str> {
env.get("error")?.get("message")?.as_str()
}
pub fn env_map(pairs: &[(&str, &str)]) -> HashMap<String, String> {
pairs
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect()
}