use std::path::PathBuf;
use rmcp::model::RawContent;
use crate::tools::{build_analyze_args, build_health_args, build_project_info_args, run_fallow};
fn fallow_binary() -> String {
if let Ok(bin) = std::env::var("FALLOW_BIN") {
return bin;
}
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.pop(); path.pop(); path.push("target/debug/fallow");
if cfg!(windows) {
path.set_extension("exe");
}
assert!(
path.is_file(),
"fallow binary not found at {path:?}. Build it first: cargo build -p fallow-cli"
);
path.to_string_lossy().to_string()
}
fn fixture_path(name: &str) -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.pop();
path.pop();
path.push("tests/fixtures");
path.push(name);
path
}
fn extract_text(result: &rmcp::model::CallToolResult) -> &str {
match &result.content[0].raw {
RawContent::Text(t) => &t.text,
_ => panic!("expected text content"),
}
}
#[tokio::test]
async fn e2e_analyze_returns_json_on_basic_project() {
let bin = fallow_binary();
let root = fixture_path("basic-project");
let params = crate::params::AnalyzeParams {
root: Some(root.to_string_lossy().to_string()),
..Default::default()
};
let args = build_analyze_args(¶ms).unwrap();
let result = run_fallow(&bin, &args).await.unwrap();
assert_eq!(result.is_error, Some(false));
let text = extract_text(&result);
let json: serde_json::Value = serde_json::from_str(text)
.unwrap_or_else(|e| panic!("should parse as JSON: {e}\ntext: {text}"));
assert!(
json.get("schema_version").is_some(),
"analyze output should have schema_version"
);
assert!(
json.get("total_issues").is_some(),
"analyze output should have total_issues"
);
}
#[tokio::test]
async fn e2e_project_info_returns_files() {
let bin = fallow_binary();
let root = fixture_path("basic-project");
let params = crate::params::ProjectInfoParams {
root: Some(root.to_string_lossy().to_string()),
..Default::default()
};
let args = build_project_info_args(¶ms);
let result = run_fallow(&bin, &args).await.unwrap();
assert_eq!(result.is_error, Some(false));
let text = extract_text(&result);
let json: serde_json::Value = serde_json::from_str(text)
.unwrap_or_else(|e| panic!("should parse as JSON: {e}\ntext: {text}"));
let file_count = json["file_count"].as_u64().unwrap_or(0);
assert!(
file_count > 0,
"project_info should report files, got file_count={file_count}"
);
}
#[tokio::test]
async fn e2e_analyze_with_issue_type_filter() {
let bin = fallow_binary();
let root = fixture_path("basic-project");
let params = crate::params::AnalyzeParams {
root: Some(root.to_string_lossy().to_string()),
issue_types: Some(vec!["unused-files".to_string()]),
..Default::default()
};
let args = build_analyze_args(¶ms).unwrap();
let result = run_fallow(&bin, &args).await.unwrap();
assert_eq!(result.is_error, Some(false));
let text = extract_text(&result);
let json: serde_json::Value = serde_json::from_str(text)
.unwrap_or_else(|e| panic!("should parse as JSON: {e}\ntext: {text}"));
assert!(
json.get("unused_files").is_some(),
"filtered output should have unused_files"
);
let exports = json["unused_exports"].as_array();
assert!(
exports.is_none() || exports.unwrap().is_empty(),
"filtered output should not have unused_exports"
);
}
#[tokio::test]
async fn e2e_health_returns_json() {
let bin = fallow_binary();
let root = fixture_path("complexity-project");
let params = crate::params::HealthParams {
root: Some(root.to_string_lossy().to_string()),
complexity: Some(true),
..Default::default()
};
let args = build_health_args(¶ms);
let result = run_fallow(&bin, &args).await.unwrap();
assert_eq!(result.is_error, Some(false));
let text = extract_text(&result);
let json: serde_json::Value = serde_json::from_str(text)
.unwrap_or_else(|e| panic!("should parse as JSON: {e}\ntext: {text}"));
assert!(json.is_object(), "health output should be a JSON object");
}