use std::fs; use std::path::{Path, PathBuf}; use serde_json::json; use sgclaw::compat::report_artifact::interpret_report_artifact_and_postprocess; use sgclaw::scene_contract::PostprocessSection; use uuid::Uuid; fn temp_workspace(prefix: &str) -> PathBuf { let root = std::env::temp_dir().join(format!("{prefix}-{}", Uuid::new_v4())); fs::create_dir_all(&root).unwrap(); root } fn report_postprocess_xlsx() -> PostprocessSection { PostprocessSection { exporter: "xlsx_report".to_string(), auto_open: None, } } fn exported_xlsx_files(workspace_root: &Path) -> Vec { let out_dir = workspace_root.join("out"); if !out_dir.exists() { return Vec::new(); } fs::read_dir(out_dir) .unwrap() .filter_map(Result::ok) .map(|entry| entry.path()) .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("xlsx")) .collect() } #[test] fn report_artifact_postprocess_exports_xlsx_for_ok_or_partial_scene() { let workspace_root = temp_workspace("sgclaw-report-artifact-export"); let artifact = json!({ "type": "report-artifact", "report_name": "tq-lineloss-report", "status": "partial", "columns": ["ORG_NAME", "LINE_LOSS_RATE"], "column_defs": [["ORG_NAME", "供电单位"], ["LINE_LOSS_RATE", "综合线损率(%)"]], "rows": [{"ORG_NAME": "国网兰州供电公司", "LINE_LOSS_RATE": "1.23"}], "counts": {"rows": 1}, "partial_reasons": ["report_log_failed"] }); let outcome = interpret_report_artifact_and_postprocess( &artifact, Some(&report_postprocess_xlsx()), &workspace_root, ) .unwrap(); assert!(outcome.success); assert!(outcome.summary.contains("tq-lineloss-report")); assert!(outcome.summary.contains("status=partial")); assert!(outcome.summary.contains("detail_rows=1")); assert!(outcome .summary .contains("partial_reasons=report_log_failed")); assert!(outcome.summary.contains("export_path=")); assert_eq!(exported_xlsx_files(&workspace_root).len(), 1); } #[test] fn report_artifact_postprocess_skips_export_for_blocked_or_error_scene() { for status in ["blocked", "error"] { let workspace_root = temp_workspace(&format!("sgclaw-report-artifact-{status}")); let artifact = json!({ "type": "report-artifact", "report_name": "generic-report", "status": status, "columns": ["ORG_NAME"], "rows": [{"ORG_NAME": "国网兰州供电公司"}], "counts": {"rows": 1}, "reasons": ["login_required"] }); let outcome = interpret_report_artifact_and_postprocess( &artifact, Some(&report_postprocess_xlsx()), &workspace_root, ) .unwrap(); assert!(!outcome.success, "{status} should fail"); assert!(outcome.summary.contains(&format!("status={status}"))); assert!(!outcome.summary.contains("export_path=")); assert!(exported_xlsx_files(&workspace_root).is_empty()); } } #[test] fn report_artifact_postprocess_exports_with_columns_when_column_defs_are_absent() { let workspace_root = temp_workspace("sgclaw-report-artifact-columns-fallback"); let artifact = json!({ "type": "report-artifact", "report_name": "generic-report", "status": "ok", "columns": ["ORG_NAME", "VALUE"], "rows": [{"ORG_NAME": "国网兰州供电公司", "VALUE": "42"}], "counts": {"rows": 1} }); let outcome = interpret_report_artifact_and_postprocess( &artifact, Some(&report_postprocess_xlsx()), &workspace_root, ) .unwrap(); assert!(outcome.success); assert!(outcome.summary.contains("generic-report")); assert!(outcome.summary.contains("detail_rows=1")); assert!(outcome.summary.contains("export_path=")); assert_eq!(exported_xlsx_files(&workspace_root).len(), 1); }