use std::collections::HashMap;
use std::path::PathBuf;
use std::process::Command;
use clap::Parser;
use socket_patch_cli::commands::list::{ListArgs, run};
use socket_patch_cli::{Cli, Commands};
use socket_patch_core::manifest::schema::{
PatchFileInfo, PatchManifest, PatchRecord, VulnerabilityInfo,
};
fn parse_list(extra: &[&str]) -> ListArgs {
let mut argv = vec!["socket-patch", "list"];
argv.extend_from_slice(extra);
let cli = Cli::try_parse_from(&argv).expect("parse");
match cli.command {
Commands::List(a) => a,
_ => panic!("expected List"),
}
}
#[test]
fn defaults_match_contract() {
let args = parse_list(&[]);
assert_eq!(args.common.cwd, PathBuf::from("."));
assert_eq!(args.common.manifest_path, ".socket/manifest.json");
assert!(!args.common.json);
}
#[test]
fn manifest_path_long_form() {
let args = parse_list(&["--manifest-path", "custom.json"]);
assert_eq!(args.common.manifest_path, "custom.json");
}
#[test]
fn cwd_long_form() {
let args = parse_list(&["--cwd", "/tmp/x"]);
assert_eq!(args.common.cwd, PathBuf::from("/tmp/x"));
}
#[test]
fn json_flag_sets_true() {
let args = parse_list(&["--json"]);
assert!(args.common.json);
}
#[test]
fn unknown_flag_is_rejected() {
let err = match Cli::try_parse_from(["socket-patch", "list", "--nope"]) {
Ok(_) => panic!("unknown flag must fail"),
Err(e) => e,
};
assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument);
}
fn populated_manifest() -> PatchManifest {
let mut files = HashMap::new();
files.insert(
"package/index.js".to_string(),
PatchFileInfo {
before_hash:
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1111"
.to_string(),
after_hash:
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb1111"
.to_string(),
},
);
let mut vulnerabilities = HashMap::new();
vulnerabilities.insert(
"GHSA-test-test-test".to_string(),
VulnerabilityInfo {
cves: vec!["CVE-2024-0001".to_string()],
summary: "test vuln".to_string(),
severity: "high".to_string(),
description: "test description".to_string(),
},
);
let mut patches = HashMap::new();
patches.insert(
"pkg:npm/test-pkg@1.0.0".to_string(),
PatchRecord {
uuid: "11111111-1111-4111-8111-111111111111".to_string(),
exported_at: "2024-01-01T00:00:00Z".to_string(),
files,
vulnerabilities,
description: "Test patch".to_string(),
license: "MIT".to_string(),
tier: "free".to_string(),
},
);
PatchManifest { patches }
}
#[tokio::test]
async fn missing_manifest_returns_1_plain() {
let tmp = tempfile::tempdir().unwrap();
let args = ListArgs {
common: socket_patch_cli::args::GlobalArgs {
cwd: tmp.path().to_path_buf(),
manifest_path: ".socket/manifest.json".into(),
json: false,
..socket_patch_cli::args::GlobalArgs::default()
},
};
assert_eq!(run(args).await, 1);
}
#[tokio::test]
async fn missing_manifest_returns_1_json() {
let tmp = tempfile::tempdir().unwrap();
let args = ListArgs {
common: socket_patch_cli::args::GlobalArgs {
cwd: tmp.path().to_path_buf(),
manifest_path: ".socket/manifest.json".into(),
json: true,
..socket_patch_cli::args::GlobalArgs::default()
},
};
assert_eq!(run(args).await, 1);
}
#[tokio::test]
async fn empty_manifest_returns_0_plain() {
let tmp = tempfile::tempdir().unwrap();
let socket_dir = tmp.path().join(".socket");
tokio::fs::create_dir_all(&socket_dir).await.unwrap();
let manifest = PatchManifest::new();
let path = socket_dir.join("manifest.json");
tokio::fs::write(&path, serde_json::to_string_pretty(&manifest).unwrap())
.await
.unwrap();
let args = ListArgs {
common: socket_patch_cli::args::GlobalArgs {
cwd: tmp.path().to_path_buf(),
manifest_path: ".socket/manifest.json".into(),
json: false,
..socket_patch_cli::args::GlobalArgs::default()
},
};
assert_eq!(run(args).await, 0);
}
#[tokio::test]
async fn empty_manifest_returns_0_json() {
let tmp = tempfile::tempdir().unwrap();
let socket_dir = tmp.path().join(".socket");
tokio::fs::create_dir_all(&socket_dir).await.unwrap();
let manifest = PatchManifest::new();
let path = socket_dir.join("manifest.json");
tokio::fs::write(&path, serde_json::to_string_pretty(&manifest).unwrap())
.await
.unwrap();
let args = ListArgs {
common: socket_patch_cli::args::GlobalArgs {
cwd: tmp.path().to_path_buf(),
manifest_path: ".socket/manifest.json".into(),
json: true,
..socket_patch_cli::args::GlobalArgs::default()
},
};
assert_eq!(run(args).await, 0);
}
#[tokio::test]
async fn populated_manifest_returns_0_plain() {
let tmp = tempfile::tempdir().unwrap();
let socket_dir = tmp.path().join(".socket");
tokio::fs::create_dir_all(&socket_dir).await.unwrap();
let manifest = populated_manifest();
let path = socket_dir.join("manifest.json");
tokio::fs::write(&path, serde_json::to_string_pretty(&manifest).unwrap())
.await
.unwrap();
let args = ListArgs {
common: socket_patch_cli::args::GlobalArgs {
cwd: tmp.path().to_path_buf(),
manifest_path: ".socket/manifest.json".into(),
json: false,
..socket_patch_cli::args::GlobalArgs::default()
},
};
assert_eq!(run(args).await, 0);
}
#[tokio::test]
async fn populated_manifest_returns_0_json() {
let tmp = tempfile::tempdir().unwrap();
let socket_dir = tmp.path().join(".socket");
tokio::fs::create_dir_all(&socket_dir).await.unwrap();
let manifest = populated_manifest();
let path = socket_dir.join("manifest.json");
tokio::fs::write(&path, serde_json::to_string_pretty(&manifest).unwrap())
.await
.unwrap();
let args = ListArgs {
common: socket_patch_cli::args::GlobalArgs {
cwd: tmp.path().to_path_buf(),
manifest_path: ".socket/manifest.json".into(),
json: true,
..socket_patch_cli::args::GlobalArgs::default()
},
};
assert_eq!(run(args).await, 0);
}
#[tokio::test]
async fn absolute_manifest_path_wins_over_cwd() {
let tmp_manifest_dir = tempfile::tempdir().unwrap();
let tmp_cwd = tempfile::tempdir().unwrap();
let manifest = PatchManifest::new();
let abs_path = tmp_manifest_dir.path().join("abs.json");
tokio::fs::write(&abs_path, serde_json::to_string_pretty(&manifest).unwrap())
.await
.unwrap();
let args = ListArgs {
common: socket_patch_cli::args::GlobalArgs {
cwd: tmp_cwd.path().to_path_buf(),
manifest_path: abs_path.to_string_lossy().into_owned(),
json: false,
..socket_patch_cli::args::GlobalArgs::default()
},
};
assert_eq!(run(args).await, 0);
}
#[test]
fn missing_manifest_json_status_is_error_via_binary() {
let tmp = tempfile::tempdir().unwrap();
let out = Command::new(env!("CARGO_BIN_EXE_socket-patch"))
.args([
"list",
"--cwd",
tmp.path().to_str().unwrap(),
"--json",
])
.output()
.expect("failed to execute socket-patch binary");
assert_eq!(
out.status.code(),
Some(1),
"missing manifest must exit 1, stderr={}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
let parsed: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("stdout must be valid JSON");
assert_eq!(parsed["command"], "list");
assert_eq!(parsed["status"], "error");
assert_eq!(parsed["error"]["code"], "manifest_not_found");
let msg = parsed["error"]["message"].as_str().expect("error message");
assert!(
msg.contains("Manifest not found"),
"error.message must include 'Manifest not found', got: {msg}"
);
}