use std::path::PathBuf;
use rstest::{fixture, rstest};
use ryo_app::{AnalysisContext, BlueprintExecutor, Goal, ParallelBlueprint, Planner};
use ryo_test::harness::{source_diff, sources_equal};
#[fixture]
fn dup_source() -> &'static str {
include_str!("fixtures/duplicate_test.rs")
}
#[fixture]
fn dup_ctx(dup_source: &'static str) -> DupTestContext {
DupTestContext::new(dup_source)
}
pub struct DupTestContext {
_temp_dir: tempfile::TempDir,
workspace_root: PathBuf,
source: String,
}
impl DupTestContext {
pub fn new(source: &str) -> Self {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let src_dir = temp_dir.path().join("src");
std::fs::create_dir_all(&src_dir).expect("Failed to create src dir");
let lib_rs = src_dir.join("lib.rs");
std::fs::write(&lib_rs, source).expect("Failed to write lib.rs");
let cargo_toml = temp_dir.path().join("Cargo.toml");
std::fs::write(
&cargo_toml,
r#"[package]
name = "test_crate"
version = "0.1.0"
edition = "2021"
"#,
)
.expect("Failed to write Cargo.toml");
Self {
workspace_root: temp_dir.path().to_path_buf(),
_temp_dir: temp_dir,
source: source.to_string(),
}
}
pub fn execute(&self, goal: &Goal) -> Result<String, String> {
let mut ctx = AnalysisContext::from_workspace_root(&self.workspace_root)
.map_err(|e| format!("Context creation error: {e}"))?;
let specs = Planner::plan(goal, Some(&ctx.registry))
.map_err(|e| format!("Planning failed: {}", e))?;
if specs.is_empty() {
return Ok(self.source.clone());
}
let blueprint = ParallelBlueprint::from_mutations(specs);
if blueprint.needs_escalation() {
return Err("Blueprint has conflicts".to_string());
}
let executor = BlueprintExecutor::default();
let result = executor.execute_v2(&blueprint, &mut ctx);
if !result.success {
return Err(result.error.unwrap_or_else(|| "Unknown error".to_string()));
}
BlueprintExecutor::sync_files_and_rebuild(&result, &mut ctx).map_err(|e| e.to_string())?;
ctx.files()
.iter()
.next()
.map(|(_, file)| file.to_source().map_err(|e| e.to_string()))
.ok_or_else(|| "No files".to_string())?
}
pub fn assert_output(&self, goal: &Goal, expected: &str) {
let result = self.execute(goal).expect("Transformation failed");
if !sources_equal(expected, &result) {
let diff = source_diff(expected, &result);
eprintln!("=== ACTUAL OUTPUT ===\n{}\n=== END ===", result);
panic!("Mismatch!\n\nDiff:\n{}", diff);
}
}
}
#[rstest]
fn test_duplicate_function(dup_ctx: DupTestContext) {
let goal: Goal = serde_json::from_str(
r#"{
"intents": [{
"type": "DuplicateFunction",
"target_fn": "handle_request",
"to": "handle_admin_request"
}]
}"#,
)
.unwrap();
let expected = include_str!("snapshots/duplicate-function.snap");
dup_ctx.assert_output(&goal, expected);
}
#[rstest]
fn test_duplicate_struct(dup_ctx: DupTestContext) {
let goal: Goal = serde_json::from_str(
r#"{
"intents": [{
"type": "DuplicateStruct",
"target_struct": "Config",
"to": "AdminConfig",
"include_impls": true
}]
}"#,
)
.unwrap();
let expected = include_str!("snapshots/duplicate-struct.snap");
dup_ctx.assert_output(&goal, expected);
}
#[rstest]
fn test_duplicate_enum(dup_ctx: DupTestContext) {
let goal: Goal = serde_json::from_str(
r#"{
"intents": [{
"type": "DuplicateEnum",
"target_enum": "Status",
"to": "AdminStatus",
"include_impls": false
}]
}"#,
)
.unwrap();
let expected = include_str!("snapshots/duplicate-enum.snap");
dup_ctx.assert_output(&goal, expected);
}
#[rstest]
fn test_duplicate_struct_cascade(dup_ctx: DupTestContext) {
let goal: Goal = serde_json::from_str(
r#"{
"intents": [{
"type": "DuplicateStruct",
"target_struct": "Config",
"to": "AdminConfig",
"include_impls": true
}]
}"#,
)
.unwrap();
let expected = include_str!("snapshots/duplicate-struct-cascade.snap");
dup_ctx.assert_output(&goal, expected);
}
#[rstest]
fn test_duplicate_enum_cascade(dup_ctx: DupTestContext) {
let goal: Goal = serde_json::from_str(
r#"{
"intents": [{
"type": "DuplicateEnum",
"target_enum": "Status",
"to": "AdminStatus",
"include_impls": true
}]
}"#,
)
.unwrap();
let expected = include_str!("snapshots/duplicate-enum-cascade.snap");
dup_ctx.assert_output(&goal, expected);
}