use anyhow::Result;
use spreadsheet_mcp::model::TableOutputFormat;
use spreadsheet_mcp::tools::{
FindValueParams, ListWorkbooksParams, ReadTableParams, SheetOverviewParams, find_value,
list_workbooks, read_table, sheet_overview,
};
use umya_spreadsheet::Spreadsheet;
mod support;
#[tokio::test(flavor = "current_thread")]
async fn sheet_overview_reports_regions_and_tools_scope_to_region() -> Result<()> {
let workspace = support::TestWorkspace::new();
let _path = workspace.create_workbook("regions.xlsx", build_regioned_workbook);
let state = workspace.app_state();
let list = list_workbooks(
state.clone(),
ListWorkbooksParams {
slug_prefix: None,
folder: None,
path_glob: None,
limit: None,
offset: None,
include_paths: None,
},
)
.await?;
let descriptor = &list.workbooks[0];
let workbook_id = descriptor.workbook_id.clone();
let overview = sheet_overview(
state.clone(),
SheetOverviewParams {
workbook_or_fork_id: workbook_id.clone(),
sheet_name: "Sheet1".to_string(),
max_regions: None,
max_headers: None,
include_headers: None,
},
)
.await?;
assert_eq!(overview.detected_regions.len(), 2);
let left = overview
.detected_regions
.iter()
.find(|r| r.bounds == "A1:B4")
.expect("left region");
let right = overview
.detected_regions
.iter()
.find(|r| r.bounds == "E1:F3")
.expect("right region");
assert!(left.confidence > 0.3);
assert!(right.confidence > 0.3);
let table = read_table(
state.clone(),
ReadTableParams {
workbook_or_fork_id: workbook_id.clone(),
sheet_name: Some("Sheet1".to_string()),
region_id: Some(left.id),
format: Some(TableOutputFormat::Json),
..Default::default()
},
)
.await?;
let values: Vec<String> = table
.rows
.iter()
.filter_map(|row| {
row.get("Month").and_then(|v| match v {
Some(spreadsheet_mcp::model::CellValue::Text(s)) => Some(s.clone()),
Some(spreadsheet_mcp::model::CellValue::Number(n)) => Some(n.to_string()),
Some(spreadsheet_mcp::model::CellValue::Bool(b)) => Some(b.to_string()),
Some(spreadsheet_mcp::model::CellValue::Date(d)) => Some(d.clone()),
Some(spreadsheet_mcp::model::CellValue::Error(e)) => Some(e.clone()),
None => None,
})
})
.collect();
assert_eq!(
values,
vec!["Jan".to_string(), "Feb".to_string(), "Mar".to_string()]
);
let find = find_value(
state.clone(),
FindValueParams {
workbook_or_fork_id: workbook_id,
sheet_name: Some("Sheet1".to_string()),
region_id: Some(right.id),
query: "Target".to_string(),
..Default::default()
},
)
.await?;
assert_eq!(find.matches.len(), 1);
assert!(find.matches[0].address.starts_with("E"));
Ok(())
}
fn build_regioned_workbook(book: &mut Spreadsheet) {
let sheet = book.get_sheet_by_name_mut("Sheet1").unwrap();
sheet.get_cell_mut("A1").set_value("Month");
sheet.get_cell_mut("B1").set_value("Value");
sheet.get_cell_mut("A2").set_value("Jan");
sheet.get_cell_mut("B2").set_value_number(10);
sheet.get_cell_mut("A3").set_value("Feb");
sheet.get_cell_mut("B3").set_value_number(20);
sheet.get_cell_mut("A4").set_value("Mar");
sheet.get_cell_mut("B4").set_value_number(30);
sheet.get_cell_mut("E1").set_value("Target");
sheet.get_cell_mut("F1").set_value("Value");
sheet.get_cell_mut("E2").set_value("North");
sheet.get_cell_mut("F2").set_value_number(5);
sheet.get_cell_mut("E3").set_value("South");
sheet.get_cell_mut("F3").set_value_number(7);
}