use super::*;
#[test]
fn sdk_migrate_batch_updates_multiple_projects_and_writes_report() {
let root = temp_dir("sdk-migrate-batch-root").expect("create temp sdk migrate batch root");
let report_path = temp_path("sdk-migrate-batch-report", "json");
let app_a = "demo_sdk_batch_app_a";
let app_b = "demo_sdk_batch_app_b";
let init_a = run_cli(&[
"sdk",
"init",
app_a,
"--template",
"action",
"--output",
root.to_str().expect("utf-8 path"),
]);
assert!(init_a.status.success(), "stderr: {}", init_a.stderr);
let init_b = run_cli(&[
"sdk",
"init",
app_b,
"--template",
"service",
"--output",
root.to_str().expect("utf-8 path"),
]);
assert!(init_b.status.success(), "stderr: {}", init_b.stderr);
let app_a_dir = root.join(app_a);
let app_b_dir = root.join(app_b);
let projects_arg = format!(
"{},{}",
app_a_dir.to_str().expect("utf-8 path"),
app_b_dir.to_str().expect("utf-8 path")
);
let output = run_cli(&[
"sdk",
"migrate-batch",
"--projects",
projects_arg.as_str(),
"--target-schema",
"4",
"--output",
report_path.to_str().expect("utf-8 path"),
"--json",
]);
assert!(output.status.success(), "stderr: {}", output.stderr);
let payload: Value =
serde_json::from_str(&output.stdout).expect("parse sdk migrate-batch json");
assert_eq!(
payload.get("api_version").and_then(Value::as_str),
Some("robotrt.sdk.migrate-batch.v1")
);
assert_eq!(payload["result"]["total_projects"].as_u64(), Some(2));
assert_eq!(payload["result"]["succeeded_projects"].as_u64(), Some(2));
assert_eq!(payload["result"]["failed_projects"].as_u64(), Some(0));
assert_eq!(payload["result"]["updated_projects"].as_u64(), Some(2));
assert!(report_path.exists());
assert!(app_a_dir.join("robotrt.migrate.manifest.json").exists());
assert!(app_b_dir.join("robotrt.migrate.manifest.json").exists());
let config_a = fs::read_to_string(app_a_dir.join("robotrt.toml")).expect("read app_a config");
assert!(config_a.contains("schema_version = 4"));
let config_b = fs::read_to_string(app_b_dir.join("robotrt.toml")).expect("read app_b config");
assert!(config_b.contains("schema_version = 4"));
}
#[test]
fn sdk_migrate_batch_reports_failures_in_json_summary() {
let root = temp_dir("sdk-migrate-batch-failure-root")
.expect("create temp sdk migrate batch failure root");
let app = "demo_sdk_batch_valid_app";
let init = run_cli(&[
"sdk",
"init",
app,
"--template",
"local",
"--output",
root.to_str().expect("utf-8 path"),
]);
assert!(init.status.success(), "stderr: {}", init.stderr);
let valid_dir = root.join(app);
let invalid_dir = root.join("missing_batch_project");
let projects_arg = format!(
"{},{}",
valid_dir.to_str().expect("utf-8 path"),
invalid_dir.to_str().expect("utf-8 path")
);
let output = run_cli(&[
"sdk",
"migrate-batch",
"--projects",
projects_arg.as_str(),
"--target-schema",
"4",
"--json",
]);
assert!(
!output.status.success(),
"batch should return non-zero when at least one project fails"
);
let payload: Value =
serde_json::from_str(&output.stdout).expect("parse sdk migrate-batch failure json");
assert_eq!(payload["result"]["total_projects"].as_u64(), Some(2));
assert_eq!(payload["result"]["succeeded_projects"].as_u64(), Some(1));
assert_eq!(payload["result"]["failed_projects"].as_u64(), Some(1));
assert_eq!(payload["result"]["stopped_early"].as_bool(), Some(false));
}
#[test]
fn sdk_migrate_batch_aggregates_critical_warn_info_severity_buckets() {
let root = temp_dir("sdk-migrate-batch-severity-root")
.expect("create temp sdk migrate batch severity root");
let app = "demo_sdk_batch_severity_app";
let init = run_cli(&[
"sdk",
"init",
app,
"--template",
"local",
"--output",
root.to_str().expect("utf-8 path"),
]);
assert!(init.status.success(), "stderr: {}", init.stderr);
let valid_dir = root.join(app);
let missing_dir = root.join("missing_batch_severity_project");
let not_dir_path = root.join("not_a_project_dir.txt");
fs::write(¬_dir_path, "placeholder").expect("write non-dir path marker");
let projects_arg = format!(
"{},{},{}",
valid_dir.to_str().expect("utf-8 path"),
missing_dir.to_str().expect("utf-8 path"),
not_dir_path.to_str().expect("utf-8 path")
);
let output = run_cli(&[
"sdk",
"migrate-batch",
"--projects",
projects_arg.as_str(),
"--target-schema",
"99",
"--json",
]);
assert!(
!output.status.success(),
"batch should return non-zero when failures exist"
);
let payload: Value =
serde_json::from_str(&output.stdout).expect("parse sdk migrate-batch severity json");
assert_eq!(payload["result"]["total_projects"].as_u64(), Some(3));
assert_eq!(payload["result"]["failed_projects"].as_u64(), Some(3));
assert_eq!(
payload["result"]["aggregate"]["failure_code_distribution"]["E_PROJECT_NOT_FOUND"].as_u64(),
Some(1)
);
assert_eq!(
payload["result"]["aggregate"]["failure_code_distribution"]["E_UNSUPPORTED_TARGET_SCHEMA"]
.as_u64(),
Some(1)
);
assert_eq!(
payload["result"]["aggregate"]["failure_code_distribution"]["E_UNKNOWN"].as_u64(),
Some(1)
);
assert_eq!(
payload["result"]["aggregate"]["failure_severity_distribution"]["critical"].as_u64(),
Some(1)
);
assert_eq!(
payload["result"]["aggregate"]["failure_severity_distribution"]["warn"].as_u64(),
Some(1)
);
assert_eq!(
payload["result"]["aggregate"]["failure_severity_distribution"]["info"].as_u64(),
Some(1)
);
}
#[test]
fn sdk_migrate_batch_supports_severity_map_override() {
let missing_dir = temp_path("sdk-migrate-batch-severity-map-missing", "dir");
let severity_map = temp_path("sdk-migrate-batch-severity-map", "json");
let severity_payload = serde_json::json!({
"E_PROJECT_NOT_FOUND": "warn"
});
fs::write(
&severity_map,
serde_json::to_string_pretty(&severity_payload).expect("serialize severity map"),
)
.expect("write severity map");
let output = run_cli(&[
"sdk",
"migrate-batch",
"--projects",
missing_dir.to_str().expect("utf-8 path"),
"--severity-map",
severity_map.to_str().expect("utf-8 path"),
"--json",
]);
assert!(!output.status.success(), "stdout: {}", output.stdout);
let payload: Value = serde_json::from_str(&output.stdout)
.expect("parse sdk migrate-batch severity override json");
assert_eq!(
payload["query"]["severity_map"].as_str(),
severity_map.to_str()
);
assert_eq!(
payload["result"]["aggregate"]["failure_severity_distribution"]["warn"].as_u64(),
Some(1)
);
}
#[test]
fn sdk_migrate_batch_supports_scan_filter_and_precheck_only() {
let root =
temp_dir("sdk-migrate-batch-precheck-root").expect("create temp sdk migrate precheck root");
let app_a = "demo_sdk_precheck_action";
let app_b = "demo_sdk_precheck_service";
let init_a = run_cli(&[
"sdk",
"init",
app_a,
"--template",
"action",
"--output",
root.to_str().expect("utf-8 path"),
]);
assert!(init_a.status.success(), "stderr: {}", init_a.stderr);
let init_b = run_cli(&[
"sdk",
"init",
app_b,
"--template",
"service",
"--output",
root.to_str().expect("utf-8 path"),
]);
assert!(init_b.status.success(), "stderr: {}", init_b.stderr);
let app_a_dir = root.join(app_a);
let migrate_a = run_cli(&[
"sdk",
"migrate",
app_a_dir.to_str().expect("utf-8 path"),
"--target-schema",
"4",
"--json",
]);
assert!(migrate_a.status.success(), "stderr: {}", migrate_a.stderr);
let precheck = run_cli(&[
"sdk",
"migrate-batch",
"--scan-root",
root.to_str().expect("utf-8 path"),
"--filter-template",
"action",
"--filter-schema",
"4",
"--target-schema",
"3",
"--precheck-only",
"--json",
]);
assert!(precheck.status.success(), "stderr: {}", precheck.stderr);
let payload: Value = serde_json::from_str(&precheck.stdout).expect("parse precheck json");
assert_eq!(payload["query"]["precheck_only"].as_bool(), Some(true));
assert_eq!(payload["result"]["candidate_projects"].as_u64(), Some(2));
assert_eq!(payload["result"]["total_projects"].as_u64(), Some(1));
assert_eq!(
payload["result"]["precheck"]["downgrade_risk_projects"].as_u64(),
Some(1)
);
assert_eq!(
payload["result"]["precheck"]["backup_conflict_projects"].as_u64(),
Some(1)
);
}
#[test]
fn sdk_rollback_batch_restores_projects_from_batch_report() {
let root = temp_dir("sdk-rollback-batch-root").expect("create temp sdk rollback batch root");
let report = temp_path("sdk-rollback-batch-report", "json");
let app_a = "demo_sdk_rollback_batch_a";
let app_b = "demo_sdk_rollback_batch_b";
for app in [app_a, app_b] {
let init = run_cli(&[
"sdk",
"init",
app,
"--template",
"local",
"--output",
root.to_str().expect("utf-8 path"),
]);
assert!(init.status.success(), "stderr: {}", init.stderr);
}
let projects_arg = format!(
"{},{}",
root.join(app_a).to_str().expect("utf-8 path"),
root.join(app_b).to_str().expect("utf-8 path")
);
let migrate = run_cli(&[
"sdk",
"migrate-batch",
"--projects",
projects_arg.as_str(),
"--target-schema",
"4",
"--output",
report.to_str().expect("utf-8 path"),
"--json",
]);
assert!(migrate.status.success(), "stderr: {}", migrate.stderr);
let rollback = run_cli(&[
"sdk",
"rollback-batch",
"--batch-report",
report.to_str().expect("utf-8 path"),
"--json",
]);
assert!(rollback.status.success(), "stderr: {}", rollback.stderr);
let payload: Value = serde_json::from_str(&rollback.stdout).expect("parse rollback-batch json");
assert_eq!(
payload.get("api_version").and_then(Value::as_str),
Some("robotrt.sdk.rollback-batch.v1")
);
assert_eq!(payload["result"]["failed_projects"].as_u64(), Some(0));
let config_a =
fs::read_to_string(root.join(app_a).join("robotrt.toml")).expect("read config a");
let config_b =
fs::read_to_string(root.join(app_b).join("robotrt.toml")).expect("read config b");
assert!(!config_a.contains("schema_version = 4"));
assert!(!config_b.contains("schema_version = 4"));
}
#[test]
fn sdk_retry_failed_retries_failed_projects_from_report() {
let root = temp_dir("sdk-retry-failed-root").expect("create temp sdk retry root");
let report = temp_path("sdk-retry-failed-report", "json");
let app = "demo_sdk_retry_valid";
let init = run_cli(&[
"sdk",
"init",
app,
"--template",
"local",
"--output",
root.to_str().expect("utf-8 path"),
]);
assert!(init.status.success(), "stderr: {}", init.stderr);
let valid_dir = root.join(app);
let missing_dir = root.join("missing_retry_project");
let projects_arg = format!(
"{},{}",
valid_dir.to_str().expect("utf-8 path"),
missing_dir.to_str().expect("utf-8 path")
);
let first_batch = run_cli(&[
"sdk",
"migrate-batch",
"--projects",
projects_arg.as_str(),
"--target-schema",
"4",
"--output",
report.to_str().expect("utf-8 path"),
"--json",
]);
assert!(
!first_batch.status.success(),
"first batch should fail due to missing project"
);
let init_missing = run_cli(&[
"sdk",
"init",
"missing_retry_project",
"--template",
"service",
"--output",
root.to_str().expect("utf-8 path"),
]);
assert!(
init_missing.status.success(),
"stderr: {}",
init_missing.stderr
);
let retry = run_cli(&[
"sdk",
"retry-failed",
"--batch-report",
report.to_str().expect("utf-8 path"),
"--target-schema",
"4",
"--json",
]);
assert!(retry.status.success(), "stderr: {}", retry.stderr);
let retry_payload: Value = serde_json::from_str(&retry.stdout).expect("parse retry json");
assert_eq!(
retry_payload.get("api_version").and_then(Value::as_str),
Some("robotrt.sdk.migrate-retry.v1")
);
assert_eq!(retry_payload["result"]["total_projects"].as_u64(), Some(1));
assert_eq!(retry_payload["result"]["failed_projects"].as_u64(), Some(0));
let retried_config =
fs::read_to_string(missing_dir.join("robotrt.toml")).expect("read retried project config");
assert!(retried_config.contains("schema_version = 4"));
}