use std::fs;
use super::helpers::{fixture_path, AftProcess};
fn temp_copy(fixture_name: &str) -> std::path::PathBuf {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let src = fixture_path(fixture_name);
let dir = std::env::temp_dir().join("aft_structure_tests");
fs::create_dir_all(&dir).unwrap();
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let (stem, ext) = fixture_name.rsplit_once('.').unwrap_or((fixture_name, ""));
let unique = if ext.is_empty() {
format!("{}_{}", stem, n)
} else {
format!("{}_{}.{}", stem, n, ext)
};
let dest = dir.join(unique);
fs::copy(&src, &dest).unwrap();
dest
}
fn send_add_derive(
aft: &mut AftProcess,
id: &str,
file: &str,
target: &str,
derives: &[&str],
) -> serde_json::Value {
let params = serde_json::json!({
"id": id,
"command": "add_derive",
"file": file,
"target": target,
"derives": derives,
});
aft.send(&serde_json::to_string(¶ms).unwrap())
}
#[test]
fn add_derive_append_to_existing() {
let tmp = temp_copy("structure_rs.rs");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_derive(&mut aft, "d1", file, "User", &["Clone"]);
assert_eq!(resp["success"], true, "response: {:?}", resp);
assert_eq!(resp["syntax_valid"], true);
let content = fs::read_to_string(&tmp).unwrap();
assert!(
content.contains("#[derive(Debug, Clone)]"),
"Expected derive to be appended. Content:\n{}",
content
);
assert!(content.contains("pub struct User"));
aft.shutdown();
}
#[test]
fn add_derive_create_new() {
let tmp = temp_copy("structure_rs.rs");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_derive(&mut aft, "d2", file, "Config", &["Debug", "Clone"]);
assert_eq!(resp["success"], true, "response: {:?}", resp);
assert_eq!(resp["syntax_valid"], true);
let content = fs::read_to_string(&tmp).unwrap();
assert!(
content.contains("#[derive(Debug, Clone)]\npub struct Config"),
"Expected new derive attribute. Content:\n{}",
content
);
aft.shutdown();
}
#[test]
fn add_derive_dedup_existing() {
let tmp = temp_copy("structure_rs.rs");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_derive(&mut aft, "d3", file, "User", &["Debug", "Clone"]);
assert_eq!(resp["success"], true, "response: {:?}", resp);
let derives: Vec<String> = resp["derives"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap().to_string())
.collect();
assert_eq!(derives, vec!["Debug", "Clone"]);
let content = fs::read_to_string(&tmp).unwrap();
assert!(
content.contains("#[derive(Debug, Clone)]"),
"Expected merged derive. Content:\n{}",
content
);
assert!(
!content.contains("#[derive(Debug, Debug"),
"Debug should not be duplicated in the derive"
);
aft.shutdown();
}
#[test]
fn add_derive_enum() {
let tmp = temp_copy("structure_rs.rs");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_derive(&mut aft, "d4", file, "Status", &["Clone"]);
assert_eq!(resp["success"], true, "response: {:?}", resp);
assert_eq!(resp["syntax_valid"], true);
let content = fs::read_to_string(&tmp).unwrap();
assert!(
content.contains("#[derive(Debug, Serialize, Clone)]"),
"Expected Clone appended to enum derive. Content:\n{}",
content
);
aft.shutdown();
}
#[test]
fn add_derive_target_not_found() {
let tmp = temp_copy("structure_rs.rs");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_derive(&mut aft, "d5", file, "Nonexistent", &["Debug"]);
assert_eq!(resp["success"], false);
assert_eq!(resp["code"], "target_not_found");
aft.shutdown();
}
fn send_wrap_try_catch(
aft: &mut AftProcess,
id: &str,
file: &str,
target: &str,
catch_body: Option<&str>,
) -> serde_json::Value {
let mut params = serde_json::json!({
"id": id,
"command": "wrap_try_catch",
"file": file,
"target": target,
});
if let Some(cb) = catch_body {
params["catch_body"] = serde_json::json!(cb);
}
aft.send(&serde_json::to_string(¶ms).unwrap())
}
#[test]
fn wrap_try_catch_simple_function() {
let tmp = temp_copy("structure_ts.ts");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_wrap_try_catch(&mut aft, "w1", file, "processData", None);
assert_eq!(resp["success"], true, "response: {:?}", resp);
assert_eq!(resp["syntax_valid"], true);
let content = fs::read_to_string(&tmp).unwrap();
assert!(
content.contains("try {"),
"Expected try block in content:\n{}",
content
);
assert!(content.contains("catch (error)"), "Expected catch block");
assert!(
content.contains("throw error;"),
"Expected default catch body"
);
assert!(
content.contains("results.push(item.toUpperCase())"),
"Body should be preserved"
);
aft.shutdown();
}
#[test]
fn wrap_try_catch_method_in_class() {
let tmp = temp_copy("structure_ts.ts");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_wrap_try_catch(
&mut aft,
"w2",
file,
"fetch",
Some("console.error(error);\nthrow error;"),
);
assert_eq!(resp["success"], true, "response: {:?}", resp);
assert_eq!(resp["syntax_valid"], true);
let content = fs::read_to_string(&tmp).unwrap();
assert!(content.contains("try {"), "Expected try block");
assert!(
content.contains("console.error(error);"),
"Expected custom catch body"
);
aft.shutdown();
}
#[test]
fn wrap_try_catch_target_not_found() {
let tmp = temp_copy("structure_ts.ts");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_wrap_try_catch(&mut aft, "w3", file, "nonexistent", None);
assert_eq!(resp["success"], false);
assert_eq!(resp["code"], "target_not_found");
aft.shutdown();
}
#[test]
fn wrap_try_catch_custom_catch_body() {
let tmp = temp_copy("structure_ts.ts");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_wrap_try_catch(&mut aft, "w4", file, "processData", Some("return [];"));
assert_eq!(resp["success"], true, "response: {:?}", resp);
let content = fs::read_to_string(&tmp).unwrap();
assert!(content.contains("return [];"), "Expected custom catch body");
aft.shutdown();
}
fn send_add_decorator(
aft: &mut AftProcess,
id: &str,
file: &str,
target: &str,
decorator: &str,
position: Option<&str>,
) -> serde_json::Value {
let mut params = serde_json::json!({
"id": id,
"command": "add_decorator",
"file": file,
"target": target,
"decorator": decorator,
});
if let Some(pos) = position {
params["position"] = serde_json::json!(pos);
}
aft.send(&serde_json::to_string(¶ms).unwrap())
}
#[test]
fn add_decorator_to_plain_function() {
let tmp = temp_copy("structure_py.py");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_decorator(&mut aft, "dec1", file, "plain_function", "cache", None);
assert_eq!(resp["success"], true, "response: {:?}", resp);
assert_eq!(resp["syntax_valid"], true);
let content = fs::read_to_string(&tmp).unwrap();
assert!(
content.contains("@cache\ndef plain_function"),
"Expected decorator before function. Content:\n{}",
content
);
aft.shutdown();
}
#[test]
fn add_decorator_to_decorated_function_first() {
let tmp = temp_copy("structure_py.py");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_decorator(
&mut aft,
"dec2",
file,
"get_users",
"login_required",
Some("first"),
);
assert_eq!(resp["success"], true, "response: {:?}", resp);
assert_eq!(resp["syntax_valid"], true);
let content = fs::read_to_string(&tmp).unwrap();
let login_pos = content.find("@login_required\n@app.route(\"/users\")");
assert!(
login_pos.is_some(),
"Expected @login_required before @app.route. Content:\n{}",
content
);
aft.shutdown();
}
#[test]
fn add_decorator_to_decorated_function_last() {
let tmp = temp_copy("structure_py.py");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_decorator(&mut aft, "dec3", file, "get_users", "cache", Some("last"));
assert_eq!(resp["success"], true, "response: {:?}", resp);
assert_eq!(resp["syntax_valid"], true);
let content = fs::read_to_string(&tmp).unwrap();
assert!(
content.contains("@app.route(\"/users\")\n@cache\ndef get_users"),
"Expected @cache between existing decorator and def. Content:\n{}",
content
);
aft.shutdown();
}
#[test]
fn add_decorator_preserves_indentation() {
let tmp = temp_copy("structure_py.py");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_decorator(&mut aft, "dec4", file, "helper", "cache", Some("first"));
assert_eq!(resp["success"], true, "response: {:?}", resp);
assert_eq!(resp["syntax_valid"], true);
let content = fs::read_to_string(&tmp).unwrap();
assert!(
content.contains(" @cache\n @staticmethod"),
"Expected indented decorator. Content:\n{}",
content
);
aft.shutdown();
}
#[test]
fn add_decorator_target_not_found() {
let tmp = temp_copy("structure_py.py");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_decorator(&mut aft, "dec5", file, "nonexistent", "cache", None);
assert_eq!(resp["success"], false);
assert_eq!(resp["code"], "target_not_found");
aft.shutdown();
}
fn send_add_struct_tags(
aft: &mut AftProcess,
id: &str,
file: &str,
target: &str,
field: &str,
tag: &str,
value: &str,
) -> serde_json::Value {
let params = serde_json::json!({
"id": id,
"command": "add_struct_tags",
"file": file,
"target": target,
"field": field,
"tag": tag,
"value": value,
});
aft.send(&serde_json::to_string(¶ms).unwrap())
}
#[test]
fn add_struct_tags_no_existing_tag() {
let tmp = temp_copy("structure_go.go");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_struct_tags(&mut aft, "t1", file, "User", "Name", "json", "name");
assert_eq!(resp["success"], true, "response: {:?}", resp);
assert_eq!(resp["syntax_valid"], true);
let content = fs::read_to_string(&tmp).unwrap();
assert!(
content.contains("Name string `json:\"name\"`"),
"Expected tag added to field. Content:\n{}",
content
);
aft.shutdown();
}
#[test]
fn add_struct_tags_with_existing_tags() {
let tmp = temp_copy("structure_go.go");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_struct_tags(&mut aft, "t2", file, "User", "Email", "xml", "email");
assert_eq!(resp["success"], true, "response: {:?}", resp);
assert_eq!(resp["syntax_valid"], true);
let content = fs::read_to_string(&tmp).unwrap();
assert!(
content.contains(r#"`json:"email" xml:"email"`"#),
"Expected both tags. Content:\n{}",
content
);
aft.shutdown();
}
#[test]
fn add_struct_tags_update_existing_value() {
let tmp = temp_copy("structure_go.go");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_struct_tags(
&mut aft,
"t3",
file,
"User",
"Email",
"json",
"email_addr,omitempty",
);
assert_eq!(resp["success"], true, "response: {:?}", resp);
let content = fs::read_to_string(&tmp).unwrap();
assert!(
content.contains(r#"`json:"email_addr,omitempty"`"#),
"Expected updated tag value. Content:\n{}",
content
);
assert!(
!content.contains(r#"`json:"email"`"#),
"Old tag value should be replaced"
);
aft.shutdown();
}
#[test]
fn add_struct_tags_preserves_other_keys() {
let tmp = temp_copy("structure_go.go");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_struct_tags(&mut aft, "t4", file, "User", "Address", "yaml", "address");
assert_eq!(resp["success"], true, "response: {:?}", resp);
let content = fs::read_to_string(&tmp).unwrap();
assert!(content.contains(r#"json:"address""#), "json tag preserved");
assert!(content.contains(r#"xml:"address""#), "xml tag preserved");
assert!(content.contains(r#"yaml:"address""#), "yaml tag added");
aft.shutdown();
}
#[test]
fn add_struct_tags_struct_not_found() {
let tmp = temp_copy("structure_go.go");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_struct_tags(&mut aft, "t5", file, "Nonexistent", "Name", "json", "name");
assert_eq!(resp["success"], false);
assert_eq!(resp["code"], "target_not_found");
aft.shutdown();
}
#[test]
fn add_struct_tags_field_not_found() {
let tmp = temp_copy("structure_go.go");
let file = tmp.to_str().unwrap();
let mut aft = AftProcess::spawn();
let resp = send_add_struct_tags(&mut aft, "t6", file, "User", "Missing", "json", "x");
assert_eq!(resp["success"], false);
assert_eq!(resp["code"], "field_not_found");
aft.shutdown();
}