spreadsheet-kit 0.11.1

Core spreadsheet automation primitives — shared types, edit normalization, and session traits for agent-facing surfaces
Documentation
use std::sync::Arc;

use anyhow::Result;
use spreadsheet_kit as spreadsheet_mcp;
use spreadsheet_mcp::model::TableOutputFormat;
use spreadsheet_mcp::repository::{VirtualWorkbookInput, VirtualWorkspaceRepository};
use spreadsheet_mcp::state::AppState;
use spreadsheet_mcp::tools::{
    ListSheetsParams, ListWorkbooksParams, RangeValuesParams, list_sheets, list_workbooks,
    range_values,
};

mod support;

#[tokio::test]
async fn path_and_virtual_readflows_match() -> Result<()> {
    let workspace = support::TestWorkspace::new();
    let path = workspace.create_workbook("parity.xlsx", |book| {
        let sheet = book.get_sheet_by_name_mut("Sheet1").unwrap();
        sheet.set_name("Data");
        sheet.get_cell_mut("A1").set_value("item");
        sheet.get_cell_mut("B1").set_value("amount");
        sheet.get_cell_mut("A2").set_value("rent");
        sheet.get_cell_mut("B2").set_value_number(1000);
        sheet.get_cell_mut("A3").set_value("food");
        sheet.get_cell_mut("B3").set_value_number(500);
    });

    let config = Arc::new(workspace.config());
    let path_state = Arc::new(AppState::new(config.clone()));

    let path_list = list_workbooks(
        path_state.clone(),
        ListWorkbooksParams {
            slug_prefix: None,
            folder: None,
            path_glob: None,
            limit: None,
            offset: None,
            include_paths: None,
        },
    )
    .await?;
    let path_id = path_list.workbooks[0].workbook_id.clone();

    let path_sheets = list_sheets(
        path_state.clone(),
        ListSheetsParams {
            workbook_or_fork_id: path_id.clone(),
            limit: None,
            offset: None,
            include_bounds: Some(true),
        },
    )
    .await?;

    let path_ranges = range_values(
        path_state.clone(),
        RangeValuesParams {
            workbook_or_fork_id: path_id,
            sheet_name: "Data".to_string(),
            ranges: vec!["A1:B3".to_string()],
            include_headers: Some(false),
            include_formulas: None,
            format: Some(TableOutputFormat::Values),
            page_size: None,
        },
    )
    .await?;

    let virtual_repo = Arc::new(VirtualWorkspaceRepository::new(config.clone()));
    let virtual_id = virtual_repo.register(VirtualWorkbookInput {
        key: "parity.xlsx".to_string(),
        slug: Some("parity".to_string()),
        bytes: std::fs::read(path)?,
    });
    let virtual_state = Arc::new(AppState::new_with_repository(config, virtual_repo));

    let virtual_sheets = list_sheets(
        virtual_state.clone(),
        ListSheetsParams {
            workbook_or_fork_id: virtual_id.clone(),
            limit: None,
            offset: None,
            include_bounds: Some(true),
        },
    )
    .await?;

    let virtual_ranges = range_values(
        virtual_state,
        RangeValuesParams {
            workbook_or_fork_id: virtual_id,
            sheet_name: "Data".to_string(),
            ranges: vec!["A1:B3".to_string()],
            include_headers: Some(false),
            include_formulas: None,
            format: Some(TableOutputFormat::Values),
            page_size: None,
        },
    )
    .await?;

    let path_sheet_names: Vec<_> = path_sheets.sheets.iter().map(|s| s.name.clone()).collect();
    let virtual_sheet_names: Vec<_> = virtual_sheets
        .sheets
        .iter()
        .map(|s| s.name.clone())
        .collect();
    assert_eq!(path_sheet_names, virtual_sheet_names);

    assert_eq!(
        serde_json::to_value(&path_ranges.values)?,
        serde_json::to_value(&virtual_ranges.values)?
    );

    Ok(())
}