use std::path::{Path, PathBuf};
#[path = "common/mod.rs"]
mod common;
use common::{assert_run_ok, git_sha256_file, has_command, pnpm_run, write_package_json};
const NPM_UUID: &str = "80630680-4da6-45f9-bba8-b888e0ffd58c";
const BEFORE_HASH: &str = "311f1e893e6eac502693fad8617dcf5353a043ccc0f7b4ba9fe385e838b67a10";
const AFTER_HASH: &str = "043f04d19e884aa5f8371428718d2a3f27a0d231afe77a2620ac6312f80aaa28";
struct TwoProjectFixture {
proj_a: PathBuf,
proj_b: PathBuf,
store_dir: PathBuf,
}
impl TwoProjectFixture {
fn index_js_in(&self, proj: &Path) -> PathBuf {
proj.join("node_modules/minimist/index.js")
}
}
fn setup_two_pnpm_projects(root: &Path) -> TwoProjectFixture {
let proj_a = root.join("proj_a");
let proj_b = root.join("proj_b");
let store_dir = root.join(".pnpm-store");
std::fs::create_dir_all(&proj_a).unwrap();
std::fs::create_dir_all(&proj_b).unwrap();
for proj in [&proj_a, &proj_b] {
std::fs::write(
proj.join("package.json"),
r#"{"name":"pnpm-fixture","version":"0.0.0","private":true,"dependencies":{"minimist":"1.2.2"}}"#,
)
.unwrap();
}
let _ = write_package_json;
let store_str = store_dir.to_str().unwrap();
let env_pairs: &[(&str, &str)] = &[];
for proj in [&proj_a, &proj_b] {
pnpm_run(
proj,
&[
"install",
"--store-dir",
store_str,
"--config.package-import-method=hardlink",
],
env_pairs,
);
}
TwoProjectFixture {
proj_a,
proj_b,
store_dir,
}
}
fn find_store_file_with_content(store_dir: &Path, expected: &[u8]) -> Option<PathBuf> {
walk_dir(store_dir, &mut |p| {
if p.is_file() {
if let Ok(c) = std::fs::read(p) {
if c == expected {
return Some(p.to_path_buf());
}
}
}
None
})
}
fn walk_dir<F>(dir: &Path, f: &mut F) -> Option<PathBuf>
where
F: FnMut(&Path) -> Option<PathBuf>,
{
let mut entries = match std::fs::read_dir(dir) {
Ok(rd) => rd,
Err(_) => return None,
};
while let Some(Ok(entry)) = entries.next() {
let p = entry.path();
if let Some(hit) = f(&p) {
return Some(hit);
}
if p.is_dir() {
if let Some(hit) = walk_dir(&p, f) {
return Some(hit);
}
}
}
None
}
#[test]
#[ignore]
fn pnpm_install_produces_symlinked_layout() {
if !has_command("pnpm") {
eprintln!("SKIP: pnpm not on PATH");
return;
}
let root = tempfile::tempdir().unwrap();
let fx = setup_two_pnpm_projects(root.path());
let nm_minimist = fx.proj_a.join("node_modules/minimist");
let lstat = std::fs::symlink_metadata(&nm_minimist)
.expect("node_modules/minimist should exist post-install");
assert!(
lstat.file_type().is_symlink(),
"pnpm should produce a symlink at node_modules/minimist"
);
let index_a = fx.index_js_in(&fx.proj_a);
assert_eq!(
git_sha256_file(&index_a),
BEFORE_HASH,
"fresh pnpm install should give us the unpatched minimist"
);
let original_bytes = std::fs::read(&index_a).unwrap();
assert!(
find_store_file_with_content(&fx.store_dir, &original_bytes).is_some(),
"store should contain a file matching proj_a's index.js"
);
}
#[test]
#[ignore]
fn apply_in_a_does_not_mutate_b_or_store() {
if !has_command("pnpm") {
eprintln!("SKIP: pnpm not on PATH");
return;
}
let root = tempfile::tempdir().unwrap();
let fx = setup_two_pnpm_projects(root.path());
let index_a = fx.index_js_in(&fx.proj_a);
let index_b = fx.index_js_in(&fx.proj_b);
assert_eq!(git_sha256_file(&index_a), BEFORE_HASH);
assert_eq!(git_sha256_file(&index_b), BEFORE_HASH);
let original_bytes = std::fs::read(&index_a).unwrap();
let store_copy = find_store_file_with_content(&fx.store_dir, &original_bytes)
.expect("store should contain the original minimist bytes pre-apply");
let store_hash_before = git_sha256_file(&store_copy);
assert_eq!(store_hash_before, BEFORE_HASH);
assert_run_ok(&fx.proj_a, &["get", NPM_UUID], "socket-patch get");
assert_eq!(
git_sha256_file(&index_a),
AFTER_HASH,
"proj_a's index.js should be patched"
);
assert_eq!(
git_sha256_file(&index_b),
BEFORE_HASH,
"proj_b's index.js must stay unpatched. CoW failure?"
);
assert_eq!(
git_sha256_file(&store_copy),
BEFORE_HASH,
"pnpm store entry must stay unpatched. CoW failure?"
);
}
#[test]
#[ignore]
fn pnpm_install_in_b_does_not_revert_a() {
if !has_command("pnpm") {
eprintln!("SKIP: pnpm not on PATH");
return;
}
let root = tempfile::tempdir().unwrap();
let fx = setup_two_pnpm_projects(root.path());
assert_run_ok(&fx.proj_a, &["get", NPM_UUID], "socket-patch get");
let index_a = fx.index_js_in(&fx.proj_a);
assert_eq!(git_sha256_file(&index_a), AFTER_HASH);
let env_pairs: &[(&str, &str)] = &[];
pnpm_run(
&fx.proj_b,
&[
"install",
"--store-dir",
fx.store_dir.to_str().unwrap(),
"--config.package-import-method=hardlink",
"--frozen-lockfile",
],
env_pairs,
);
assert_eq!(
git_sha256_file(&index_a),
AFTER_HASH,
"proj_a's patch must survive `pnpm install --frozen-lockfile` in proj_b"
);
assert_eq!(
git_sha256_file(&fx.index_js_in(&fx.proj_b)),
BEFORE_HASH,
"proj_b should still see the original minimist after frozen install"
);
}
#[test]
#[ignore]
fn apply_in_pnpm_project_emits_layout_note() {
if !has_command("pnpm") {
eprintln!("SKIP: pnpm not on PATH");
return;
}
let root = tempfile::tempdir().unwrap();
let fx = setup_two_pnpm_projects(root.path());
let (_stdout, stderr) =
assert_run_ok(&fx.proj_a, &["get", NPM_UUID], "socket-patch get");
assert!(
stderr.to_lowercase().contains("pnpm"),
"apply against a pnpm project should mention pnpm in stderr.\nstderr:\n{stderr}"
);
}