use std::fs; use std::path::PathBuf; use serde_json::json; use sgclaw::compat::lineloss_xlsx_export::{export_lineloss_xlsx, LinelossExportRequest}; fn temp_output_path(name: &str) -> PathBuf { let dir = std::env::temp_dir().join("sgclaw-test-xlsx"); fs::create_dir_all(&dir).unwrap(); dir.join(name) } #[test] fn export_month_lineloss_produces_valid_xlsx() { let output_path = temp_output_path("month-test.xlsx"); if output_path.exists() { fs::remove_file(&output_path).unwrap(); } let request = LinelossExportRequest { sheet_name: "国网兰州供电公司月度线损分析报表(2026-03)".to_string(), column_defs: vec![ ("ORG_NAME".to_string(), "供电单位".to_string()), ("YGDL".to_string(), "累计供电量".to_string()), ("YYDL".to_string(), "累计售电量".to_string()), ("YXSL".to_string(), "线损完成率(%)".to_string()), ("RAT_SCOPE".to_string(), "线损率累计目标值".to_string()), ("BLANK3".to_string(), "目标完成率".to_string()), ("BLANK2".to_string(), "排行".to_string()), ], rows: vec![ serde_json::from_value(json!({ "ORG_NAME": "城关供电", "YGDL": "12345.67", "YYDL": "11234.56", "YXSL": "9.00", "RAT_SCOPE": "9.50", "BLANK3": "94.74", "BLANK2": "1" })) .unwrap(), serde_json::from_value(json!({ "ORG_NAME": "七里河供电", "YGDL": "9876.54", "YYDL": "8765.43", "YXSL": "11.24", "RAT_SCOPE": "10.00", "BLANK3": "112.40", "BLANK2": "2" })) .unwrap(), ], output_path: output_path.clone(), }; let result_path = export_lineloss_xlsx(&request).unwrap(); assert_eq!(result_path, output_path); assert!(output_path.exists()); // Verify it's a valid ZIP (xlsx is a zip archive) let file = fs::File::open(&output_path).unwrap(); let mut archive = zip::ZipArchive::new(file).unwrap(); // Must contain the standard OpenXML entries let entry_names: Vec = (0..archive.len()) .map(|i| archive.by_index(i).unwrap().name().to_string()) .collect(); assert!(entry_names.contains(&"[Content_Types].xml".to_string())); assert!(entry_names.contains(&"xl/worksheets/sheet1.xml".to_string())); assert!(entry_names.contains(&"xl/workbook.xml".to_string())); // Read sheet1.xml and verify it contains our data let mut sheet = archive.by_name("xl/worksheets/sheet1.xml").unwrap(); let mut xml = String::new(); std::io::Read::read_to_string(&mut sheet, &mut xml).unwrap(); assert!( xml.contains("供电单位"), "header row should contain 供电单位" ); assert!( xml.contains("累计供电量"), "header row should contain 累计供电量" ); assert!(xml.contains("城关供电"), "data should contain 城关供电"); assert!(xml.contains("12345.67"), "data should contain 12345.67"); assert!(xml.contains("七里河供电"), "data should contain second row"); // Cleanup fs::remove_file(&output_path).unwrap(); } #[test] fn export_empty_rows_returns_error() { let output_path = temp_output_path("empty-test.xlsx"); let request = LinelossExportRequest { sheet_name: "test".to_string(), column_defs: vec![("A".to_string(), "ColA".to_string())], rows: vec![], output_path: output_path.clone(), }; let result = export_lineloss_xlsx(&request); assert!(result.is_err()); assert!( result .unwrap_err() .to_string() .contains("rows must not be empty"), "should reject empty rows" ); }