use super::*;
use std::fs;
fn request(root: &str, pattern: &str, substitute: &str) -> RefactorRequest {
RefactorRequest {
root: root.to_string(),
pairs: vec![PatternPairRequest {
pattern: pattern.to_string(),
substitute: substitute.to_string(),
}],
files_only: false,
dirs_only: false,
names_only: false,
content_only: false,
ignore_case: false,
use_regex: false,
include_hidden: false,
binary_names: false,
follow_symlinks: false,
backup: false,
gitignore: false,
max_depth: 0,
exclude: Vec::new(),
include: Vec::new(),
}
}
#[test]
fn test11078_a_relative_root_is_resolved_against_the_workspace() {
let workspace = tempfile::TempDir::new().expect("temp dir");
fs::create_dir_all(workspace.path().join("cartridges/txtcartridge")).expect("nested");
let args = request("cartridges/txtcartridge", "a", "b")
.to_args(workspace.path())
.expect("valid");
assert_eq!(
wsb::platform::canonicalize(&args.root_dir).expect("exists"),
wsb::platform::canonicalize(workspace.path().join("cartridges/txtcartridge")).expect("exists")
);
let elsewhere = tempfile::TempDir::new().expect("temp dir");
let absolute = request(&elsewhere.path().display().to_string(), "a", "b")
.to_args(workspace.path())
.expect("valid");
assert_eq!(absolute.root_dir, elsewhere.path());
}
#[test]
fn test11079_every_option_reaches_the_engine() {
let dir = tempfile::TempDir::new().expect("temp dir");
let mut req = request(&dir.path().display().to_string(), "a", "b");
req.files_only = true;
req.ignore_case = true;
req.use_regex = true;
req.include_hidden = true;
req.binary_names = true;
req.follow_symlinks = true;
req.backup = true;
req.gitignore = true;
req.max_depth = 3;
req.exclude = vec!["*.lock".to_string()];
req.include = vec!["*.rs".to_string()];
let args = req.to_args(dir.path()).expect("valid");
assert!(args.files_only);
assert!(args.ignore_case);
assert!(args.use_regex);
assert!(args.include_hidden);
assert!(args.binary_names);
assert!(args.follow_symlinks);
assert!(args.backup);
assert!(args.gitignore);
assert_eq!(args.max_depth, 3);
assert_eq!(args.exclude_patterns, vec!["*.lock".to_string()]);
assert_eq!(args.include_patterns, vec!["*.rs".to_string()]);
}
#[test]
fn test11080_a_combination_the_command_line_refuses_is_refused_here() {
let dir = tempfile::TempDir::new().expect("temp dir");
let mut req = request(&dir.path().display().to_string(), "a", "b");
req.files_only = true;
req.dirs_only = true;
let refused = req.to_args(dir.path()).expect_err("mutually exclusive");
assert!(
refused.contains("more than one mode"),
"the refusal says which rule: {refused}"
);
}
#[test]
fn test11081_a_page_never_runs_off_the_end_of_a_plan() {
let dir = tempfile::TempDir::new().expect("temp dir");
fs::write(dir.path().join("one.txt"), "widget\n").expect("file");
let args = request(&dir.path().display().to_string(), "widget", "gadget")
.to_args(dir.path())
.expect("valid");
let engine = RenameEngine::new(args).expect("engine");
let (plan, _, _) = engine.plan().expect("a plan");
assert!(plan.page(plan.total(), DEFAULT_LIMIT).is_empty());
assert!(plan.page(usize::MAX / 2, DEFAULT_LIMIT).is_empty());
}
#[test]
fn test11083_an_unknown_plan_says_why_it_is_gone() {
let (status, body) = held(Uuid::new_v4()).expect_err("no such plan");
assert_eq!(status, StatusCode::NOT_FOUND);
let text = body.0.to_string();
assert!(text.contains("Preview it again"), "actionable: {text}");
}
#[test]
fn test11099_several_pairs_are_carried_and_applied_in_order() {
let dir = tempfile::TempDir::new().expect("temp dir");
fs::write(dir.path().join("a.txt"), "alpha beta gamma\n").expect("file");
let mut req = request(&dir.path().display().to_string(), "alpha", "one");
req.pairs.push(PatternPairRequest {
pattern: "beta".to_string(),
substitute: "two".to_string(),
});
req.pairs.push(PatternPairRequest {
pattern: "gamma".to_string(),
substitute: "three".to_string(),
});
let args = req.to_args(dir.path()).expect("valid");
let engine = RenameEngine::new_with_pairs(args, req.pairs()).expect("engine");
let (plan, _, _) = engine.plan().expect("a plan");
assert_eq!(plan.replacements, 3);
assert_eq!(plan.files_edited, 1);
engine.apply(&plan).expect("applies");
assert_eq!(
fs::read_to_string(dir.path().join("a.txt")).expect("read"),
"one two three\n"
);
}
#[test]
fn test11100_a_later_pair_cannot_smuggle_a_path_separator() {
let dir = tempfile::TempDir::new().expect("temp dir");
fs::write(dir.path().join("widget.txt"), "widget\n").expect("file");
let mut req = request(&dir.path().display().to_string(), "widget", "gadget");
req.pairs.push(PatternPairRequest {
pattern: "gadget".to_string(),
substitute: "../escaped".to_string(),
});
let args = req.to_args(dir.path()).expect("the first pair is fine");
let refused = match RenameEngine::new_with_pairs(args, req.pairs()) {
Ok(_) => panic!("a later pair with a separator must be refused"),
Err(error) => error.to_string(),
};
assert!(
refused.contains("path-unsafe"),
"the refusal names the rule: {refused}"
);
}
#[test]
fn test11101_a_refactor_with_no_pairs_is_refused() {
let dir = tempfile::TempDir::new().expect("temp dir");
let mut req = request(&dir.path().display().to_string(), "a", "b");
req.pairs.clear();
let refused = req.to_args(dir.path()).expect_err("nothing to do");
assert!(refused.contains("at least one pattern"), "{refused}");
}
#[test]
fn test11131_a_diff_shows_the_change_with_line_numbers_on_both_sides() {
let dir = tempfile::TempDir::new().expect("temp dir");
fs::write(dir.path().join("a.txt"), "one\nTARGET\nthree\n").expect("file");
let req = request(&dir.path().display().to_string(), "TARGET", "A\nB");
let args = req.to_args(dir.path()).expect("valid");
let engine = RenameEngine::new_with_pairs(args, req.pairs()).expect("engine");
let hunks = engine
.hunks_for(&dir.path().join("a.txt"), 1)
.expect("a diff");
let lines: Vec<String> = hunks
.iter()
.flat_map(|h| &h.lines)
.map(|line| match line {
wsb::refactor::textdiff::DiffLine::Context { old, new, text, .. } => {
format!("{old}|{new}| |{text}")
}
wsb::refactor::textdiff::DiffLine::Removed { old, text, .. } => format!("{old}|-|-|{text}"),
wsb::refactor::textdiff::DiffLine::Added { new, text, .. } => format!("-|{new}|+|{text}"),
})
.collect();
assert_eq!(
lines,
vec!["1|1| |one", "2|-|-|TARGET", "-|2|+|A", "-|3|+|B", "3|4| |three"]
);
}
#[test]
fn test11132_a_diff_uses_the_same_rules_the_write_will_use() {
let dir = tempfile::TempDir::new().expect("temp dir");
fs::write(dir.path().join("a.rs"), "fn build(x)\n").expect("file");
let mut req = request(&dir.path().display().to_string(), r"fn (\w+)\(", "fn new_$1(");
req.use_regex = true;
req.content_only = true;
let args = req.to_args(dir.path()).expect("valid");
let engine = RenameEngine::new_with_pairs(args, req.pairs()).expect("engine");
let hunks = engine.hunks_for(&dir.path().join("a.rs"), 0).expect("a diff");
let shown = format!("{hunks:?}");
assert!(shown.contains("fn new_build(x)"), "the diff expands the capture: {shown}");
let (plan, _, _) = engine.plan().expect("a plan");
engine.apply(&plan).expect("applies");
assert_eq!(
fs::read_to_string(dir.path().join("a.rs")).expect("read"),
"fn new_build(x)\n"
);
}
#[test]
fn test11133_a_file_the_plan_never_listed_cannot_be_read_through_the_diff() {
let dir = tempfile::TempDir::new().expect("temp dir");
fs::write(dir.path().join("listed.txt"), "widget\n").expect("file");
fs::write(dir.path().join("secret.txt"), "nothing to match\n").expect("other");
let req = request(&dir.path().display().to_string(), "widget", "gadget");
let args = req.to_args(dir.path()).expect("valid");
let engine = RenameEngine::new_with_pairs(args, req.pairs()).expect("engine");
let (plan, _, _) = engine.plan().expect("a plan");
let listed = |path: &str| {
plan.changes.iter().any(|change| {
matches!(change, wsb::refactor::preview::Change::Content { path: p, .. } if p == path)
})
};
assert!(listed("listed.txt"));
assert!(!listed("secret.txt"), "a file with no change is not listed");
assert!(!listed("../../etc/passwd"));
}