f7a720204a
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
275 lines
8.4 KiB
Rust
275 lines
8.4 KiB
Rust
use nomifun_api_types::ConversionTarget;
|
|
use nomifun_office::ConversionService;
|
|
use rust_xlsxwriter::{Format, Workbook};
|
|
use std::path::PathBuf;
|
|
use tempfile::TempDir;
|
|
|
|
fn create_simple_xlsx(dir: &TempDir) -> PathBuf {
|
|
let path = dir.path().join("test.xlsx");
|
|
let mut wb = Workbook::new();
|
|
let sheet = wb.add_worksheet();
|
|
sheet.set_name("Sheet1").unwrap();
|
|
sheet.write_string(0, 0, "Name").unwrap();
|
|
sheet.write_string(0, 1, "Age").unwrap();
|
|
sheet.write_string(1, 0, "Alice").unwrap();
|
|
sheet.write_number(1, 1, 30.0).unwrap();
|
|
sheet.write_string(2, 0, "Bob").unwrap();
|
|
sheet.write_number(2, 1, 25.0).unwrap();
|
|
wb.save(&path).unwrap();
|
|
path
|
|
}
|
|
|
|
fn create_multi_sheet_xlsx(dir: &TempDir) -> PathBuf {
|
|
let path = dir.path().join("multi.xlsx");
|
|
let mut wb = Workbook::new();
|
|
|
|
let s1 = wb.add_worksheet();
|
|
s1.set_name("Users").unwrap();
|
|
s1.write_string(0, 0, "Name").unwrap();
|
|
s1.write_number(0, 1, 1.0).unwrap();
|
|
|
|
let s2 = wb.add_worksheet();
|
|
s2.set_name("Products").unwrap();
|
|
s2.write_string(0, 0, "Item").unwrap();
|
|
s2.write_number(0, 1, 9.99).unwrap();
|
|
|
|
let s3 = wb.add_worksheet();
|
|
s3.set_name("Empty").unwrap();
|
|
|
|
wb.save(&path).unwrap();
|
|
path
|
|
}
|
|
|
|
fn create_xlsx_with_merges(dir: &TempDir) -> PathBuf {
|
|
let path = dir.path().join("merged.xlsx");
|
|
let mut wb = Workbook::new();
|
|
let sheet = wb.add_worksheet();
|
|
sheet.set_name("Merged").unwrap();
|
|
let fmt = Format::new();
|
|
sheet.merge_range(0, 0, 1, 2, "Merged Title", &fmt).unwrap();
|
|
sheet.write_string(2, 0, "A").unwrap();
|
|
sheet.write_string(2, 1, "B").unwrap();
|
|
sheet.write_string(2, 2, "C").unwrap();
|
|
wb.save(&path).unwrap();
|
|
path
|
|
}
|
|
|
|
#[allow(clippy::approx_constant)] // 3.14 is test data, not an approximation of PI
|
|
fn create_xlsx_with_types(dir: &TempDir) -> PathBuf {
|
|
let path = dir.path().join("types.xlsx");
|
|
let mut wb = Workbook::new();
|
|
let sheet = wb.add_worksheet();
|
|
sheet.set_name("Types").unwrap();
|
|
sheet.write_string(0, 0, "text").unwrap();
|
|
sheet.write_number(0, 1, 42.0).unwrap();
|
|
sheet.write_number(0, 2, 3.14).unwrap();
|
|
sheet.write_boolean(0, 3, true).unwrap();
|
|
wb.save(&path).unwrap();
|
|
path
|
|
}
|
|
|
|
// DC-1: Excel → JSON (normal)
|
|
#[tokio::test]
|
|
async fn dc1_excel_to_json_simple() {
|
|
let dir = TempDir::new().unwrap();
|
|
let path = create_simple_xlsx(&dir);
|
|
let svc = ConversionService::new(None);
|
|
|
|
let resp = svc
|
|
.convert(path.to_str().unwrap(), ConversionTarget::ExcelJson)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(resp.to, "excel-json");
|
|
assert!(resp.result.success);
|
|
assert!(resp.result.error.is_none());
|
|
|
|
let data = resp.result.data.unwrap();
|
|
let sheets = data["sheets"].as_array().unwrap();
|
|
assert_eq!(sheets.len(), 1);
|
|
assert_eq!(sheets[0]["name"], "Sheet1");
|
|
|
|
let rows = sheets[0]["data"].as_array().unwrap();
|
|
assert_eq!(rows.len(), 3);
|
|
assert_eq!(rows[0][0], "Name");
|
|
assert_eq!(rows[0][1], "Age");
|
|
assert_eq!(rows[1][0], "Alice");
|
|
assert_eq!(rows[1][1], 30.0);
|
|
assert_eq!(rows[2][0], "Bob");
|
|
assert_eq!(rows[2][1], 25.0);
|
|
}
|
|
|
|
// DC-2: Excel → JSON (multiple sheets)
|
|
#[tokio::test]
|
|
async fn dc2_excel_to_json_multi_sheet() {
|
|
let dir = TempDir::new().unwrap();
|
|
let path = create_multi_sheet_xlsx(&dir);
|
|
let svc = ConversionService::new(None);
|
|
|
|
let resp = svc
|
|
.convert(path.to_str().unwrap(), ConversionTarget::ExcelJson)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(resp.result.success);
|
|
let data = resp.result.data.unwrap();
|
|
let sheets = data["sheets"].as_array().unwrap();
|
|
assert_eq!(sheets.len(), 3);
|
|
assert_eq!(sheets[0]["name"], "Users");
|
|
assert_eq!(sheets[1]["name"], "Products");
|
|
assert_eq!(sheets[2]["name"], "Empty");
|
|
}
|
|
|
|
// DC-3: Excel → JSON (with merged cells)
|
|
#[tokio::test]
|
|
async fn dc3_excel_to_json_with_merges() {
|
|
let dir = TempDir::new().unwrap();
|
|
let path = create_xlsx_with_merges(&dir);
|
|
let svc = ConversionService::new(None);
|
|
|
|
let resp = svc
|
|
.convert(path.to_str().unwrap(), ConversionTarget::ExcelJson)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(resp.result.success);
|
|
let data = resp.result.data.unwrap();
|
|
let sheet = &data["sheets"][0];
|
|
assert_eq!(sheet["name"], "Merged");
|
|
|
|
let merges = sheet["merges"].as_array().unwrap();
|
|
assert!(!merges.is_empty());
|
|
|
|
let merge = &merges[0];
|
|
assert_eq!(merge["s"]["r"], 0);
|
|
assert_eq!(merge["s"]["c"], 0);
|
|
assert_eq!(merge["e"]["r"], 1);
|
|
assert_eq!(merge["e"]["c"], 2);
|
|
}
|
|
|
|
// DC-4: Excel → JSON (file not found)
|
|
#[tokio::test]
|
|
async fn dc4_excel_to_json_file_not_found() {
|
|
let svc = ConversionService::new(None);
|
|
let resp = svc
|
|
.convert("/nonexistent/file.xlsx", ConversionTarget::ExcelJson)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(resp.to, "excel-json");
|
|
assert!(!resp.result.success);
|
|
assert!(resp.result.data.is_none());
|
|
assert!(resp.result.error.as_ref().unwrap().contains("file not found"));
|
|
}
|
|
|
|
// DC-6: Word → Markdown (pandoc not available)
|
|
#[tokio::test]
|
|
async fn dc6_word_to_markdown_file_not_found() {
|
|
let svc = ConversionService::new(None);
|
|
let resp = svc
|
|
.convert("/nonexistent/file.docx", ConversionTarget::Markdown)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(resp.to, "markdown");
|
|
assert!(!resp.result.success);
|
|
assert!(resp.result.error.as_ref().unwrap().contains("file not found"));
|
|
}
|
|
|
|
// DC-8: PPT → JSON (officecli not available)
|
|
#[tokio::test]
|
|
async fn dc8_ppt_to_json_file_not_found() {
|
|
let svc = ConversionService::new(None);
|
|
let resp = svc
|
|
.convert("/nonexistent/file.pptx", ConversionTarget::PptJson)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(resp.to, "ppt-json");
|
|
assert!(!resp.result.success);
|
|
assert!(resp.result.error.as_ref().unwrap().contains("file not found"));
|
|
}
|
|
|
|
// DC-8b: PPT → JSON (officecli not installed — configured path invalid and not in PATH)
|
|
#[tokio::test]
|
|
async fn dc8b_ppt_to_json_officecli_not_installed() {
|
|
let dir = TempDir::new().unwrap();
|
|
let path = dir.path().join("slides.pptx");
|
|
std::fs::write(&path, b"fake pptx content").unwrap();
|
|
|
|
// Use a wrapper that ensures officecli is not found
|
|
let svc = ConversionService::new(Some("/nonexistent/officecli".into()));
|
|
let resp = svc
|
|
.convert(path.to_str().unwrap(), ConversionTarget::PptJson)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Conversion should fail — either because officecli is not found or because
|
|
// it fails to parse the fake file. Either way, success must be false.
|
|
assert!(!resp.result.success);
|
|
assert!(resp.result.error.is_some());
|
|
}
|
|
|
|
// Excel cell type handling
|
|
#[tokio::test]
|
|
#[allow(clippy::approx_constant)] // 3.14 is test data, not an approximation of PI
|
|
async fn excel_to_json_cell_types() {
|
|
let dir = TempDir::new().unwrap();
|
|
let path = create_xlsx_with_types(&dir);
|
|
let svc = ConversionService::new(None);
|
|
|
|
let resp = svc
|
|
.convert(path.to_str().unwrap(), ConversionTarget::ExcelJson)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(resp.result.success);
|
|
let data = resp.result.data.unwrap();
|
|
let row = &data["sheets"][0]["data"][0];
|
|
|
|
assert_eq!(row[0], "text");
|
|
assert_eq!(row[1], 42.0);
|
|
assert!((row[2].as_f64().unwrap() - 3.14).abs() < 0.01);
|
|
assert_eq!(row[3], true);
|
|
}
|
|
|
|
// Excel empty file
|
|
#[tokio::test]
|
|
async fn excel_to_json_empty_workbook() {
|
|
let dir = TempDir::new().unwrap();
|
|
let path = dir.path().join("empty.xlsx");
|
|
let mut wb = Workbook::new();
|
|
wb.add_worksheet().set_name("Empty").unwrap();
|
|
wb.save(&path).unwrap();
|
|
|
|
let svc = ConversionService::new(None);
|
|
let resp = svc
|
|
.convert(path.to_str().unwrap(), ConversionTarget::ExcelJson)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(resp.result.success);
|
|
let data = resp.result.data.unwrap();
|
|
let sheets = data["sheets"].as_array().unwrap();
|
|
assert_eq!(sheets.len(), 1);
|
|
assert_eq!(sheets[0]["name"], "Empty");
|
|
}
|
|
|
|
// Response always returns success=true at convert() level (error wrapped in result)
|
|
#[tokio::test]
|
|
async fn convert_always_returns_ok_with_result_wrapper() {
|
|
let svc = ConversionService::new(None);
|
|
|
|
let targets = [
|
|
ConversionTarget::Markdown,
|
|
ConversionTarget::ExcelJson,
|
|
ConversionTarget::PptJson,
|
|
];
|
|
|
|
for target in targets {
|
|
let resp = svc.convert("/nonexistent/path", target).await;
|
|
assert!(resp.is_ok());
|
|
assert!(!resp.unwrap().result.success);
|
|
}
|
|
}
|