use chrono::TimeZone;
use formualizer_common::LiteralValue;
use formualizer_eval::engine::DeterministicMode;
use formualizer_eval::timezone::TimeZoneSpec;
use formualizer_eval::traits::VolatileLevel;
use formualizer_sheetport::{EvalOptions, InputUpdate, PortValue, SheetPort, SheetPortSession};
use formualizer_workbook::Workbook;
use sheetport_spec::Manifest;
fn make_test_workbook() -> Workbook {
let mut wb = Workbook::new();
wb.add_sheet("Inputs").unwrap();
wb.add_sheet("Inventory").unwrap();
wb.add_sheet("Outputs").unwrap();
wb
}
#[test]
fn sheetport_initializes_with_matching_workbook() {
let yaml = include_str!("../../sheetport-spec/tests/fixtures/supply_planning.yaml");
let manifest: Manifest = Manifest::from_yaml_str(yaml).expect("fixture parses");
let mut workbook = make_test_workbook();
SheetPort::new(&mut workbook, manifest).expect("sheetport binds to workbook");
}
#[test]
fn sheetport_fails_for_missing_sheet() {
let yaml = include_str!("../../sheetport-spec/tests/fixtures/supply_planning.yaml");
let manifest: Manifest = Manifest::from_yaml_str(yaml).expect("fixture parses");
let mut workbook = Workbook::new();
workbook.add_sheet("Inputs").unwrap();
workbook.add_sheet("Outputs").unwrap();
let err = match SheetPort::new(&mut workbook, manifest) {
Ok(_) => panic!("expected missing sheet error"),
Err(err) => err,
};
match err {
formualizer_sheetport::SheetPortError::MissingSheet { port, sheet } => {
assert_eq!(port, "sku_inventory");
assert_eq!(sheet, "Inventory");
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn sheetport_session_supports_owned_workbook() {
let manifest_yaml = r#"
spec: fio
spec_version: "0.3.0"
manifest:
id: session-test
name: Session Test
workbook:
uri: memory://session.xlsx
locale: en-US
date_system: 1900
ports:
- id: input_a
dir: in
shape: scalar
location:
a1: Sheet!A1
schema:
type: number
- id: output_b
dir: out
shape: scalar
location:
a1: Sheet!B1
schema:
type: number
"#;
let manifest: Manifest =
Manifest::from_yaml_str(manifest_yaml).expect("session manifest parses");
let mut workbook = Workbook::new();
workbook.add_sheet("Sheet").unwrap();
workbook
.set_value("Sheet", 1, 1, LiteralValue::Number(5.0))
.expect("set A1");
workbook
.set_value("Sheet", 1, 2, LiteralValue::Number(10.0))
.expect("set B1");
let mut session = SheetPortSession::new(workbook, manifest).expect("session created");
let inputs = session.read_inputs().expect("read inputs");
match inputs.get("input_a") {
Some(PortValue::Scalar(LiteralValue::Number(n))) => assert_eq!(*n, 5.0),
other => panic!("unexpected input value: {other:?}"),
}
let mut update = InputUpdate::new();
update.insert("input_a", PortValue::Scalar(LiteralValue::Number(7.5)));
session.write_inputs(update).expect("write inputs");
assert_eq!(
session
.workbook()
.get_value("Sheet", 1, 1)
.unwrap_or(LiteralValue::Empty),
LiteralValue::Number(7.5)
);
let outputs = session
.evaluate_once(EvalOptions::default())
.expect("evaluate once");
match outputs.get("output_b") {
Some(PortValue::Scalar(LiteralValue::Number(n))) => assert_eq!(*n, 10.0),
other => panic!("unexpected output value: {other:?}"),
}
}
#[test]
fn evaluate_once_builds_all_staged_formulas_before_targeted_sheetport_eval() {
let manifest_yaml = r#"
spec: fio
spec_version: "0.3.0"
manifest:
id: staged-cross-sheet
name: Staged Cross Sheet
workbook:
uri: memory://staged-cross-sheet.xlsx
locale: en-US
date_system: 1900
ports:
- id: output_a
dir: out
shape: scalar
location:
a1: Outputs!A1
schema:
type: number
"#;
let manifest: Manifest = Manifest::from_yaml_str(manifest_yaml).expect("manifest parses");
let mut workbook = Workbook::new();
workbook.add_sheet("Inputs").unwrap();
workbook.add_sheet("Calc").unwrap();
workbook.add_sheet("Outputs").unwrap();
workbook
.set_value("Inputs", 1, 1, LiteralValue::Number(10.0))
.expect("set input");
workbook
.set_formula("Calc", 1, 1, "=Inputs!A1*2")
.expect("stage calc formula");
workbook
.set_formula("Outputs", 1, 1, "=Calc!A1+1")
.expect("stage output formula");
assert!(workbook.has_staged_formulas());
let mut sheetport = SheetPort::new(&mut workbook, manifest).expect("sheetport binds");
let outputs = sheetport
.evaluate_once(EvalOptions::default())
.expect("evaluate once");
match outputs.get("output_a") {
Some(PortValue::Scalar(LiteralValue::Number(n))) => assert_eq!(*n, 21.0),
other => panic!("unexpected output value: {other:?}"),
}
assert_eq!(
sheetport
.workbook()
.get_value("Outputs", 1, 1)
.unwrap_or(LiteralValue::Empty),
LiteralValue::Number(21.0)
);
assert!(!sheetport.workbook().has_staged_formulas());
}
#[test]
fn evaluate_once_invalid_deterministic_mode_does_not_leak_other_overrides() {
let manifest_yaml = r#"
spec: fio
spec_version: "0.3.0"
manifest:
id: deterministic-leak-test
name: Deterministic Leak Test
workbook:
uri: memory://deterministic-leak.xlsx
locale: en-US
date_system: 1900
ports:
- id: output_a
dir: out
shape: scalar
location:
a1: Sheet!A1
schema:
type: number
"#;
let manifest: Manifest = Manifest::from_yaml_str(manifest_yaml).expect("manifest parses");
let mut workbook = Workbook::new();
workbook.add_sheet("Sheet").unwrap();
workbook
.set_value("Sheet", 1, 1, LiteralValue::Number(1.0))
.expect("set A1");
workbook.engine_mut().set_workbook_seed(7);
workbook
.engine_mut()
.set_volatile_level(VolatileLevel::Always);
let original_mode = workbook.engine().config.deterministic_mode.clone();
let mut sheetport = SheetPort::new(&mut workbook, manifest).expect("sheetport binds");
let timestamp = chrono::Utc
.with_ymd_and_hms(2025, 1, 1, 0, 0, 0)
.single()
.expect("valid timestamp");
let err = sheetport
.evaluate_once(EvalOptions {
freeze_volatile: true,
rng_seed: Some(123),
deterministic_mode: Some(DeterministicMode::Enabled {
timestamp_utc: timestamp,
timezone: TimeZoneSpec::Local,
}),
..Default::default()
})
.expect_err("local timezone should be rejected in deterministic mode");
match err {
formualizer_sheetport::SheetPortError::Engine { .. } => {}
other => panic!("unexpected error: {other:?}"),
}
assert_eq!(sheetport.workbook().engine().config.workbook_seed, 7);
assert_eq!(
sheetport.workbook().engine().config.volatile_level,
VolatileLevel::Always
);
assert_eq!(
&sheetport.workbook().engine().config.deterministic_mode,
&original_mode
);
}
#[test]
fn sheetport_rejects_unsupported_profile() {
let manifest_yaml = r#"
spec: fio
spec_version: "0.3.0"
capabilities: { profile: full-v0 }
manifest: { id: profile-test, name: Profile Test }
ports:
- id: input_a
dir: in
shape: scalar
location:
a1: Sheet!A1
schema:
type: number
"#;
let manifest: Manifest = Manifest::from_yaml_str(manifest_yaml).expect("manifest parses");
let mut workbook = Workbook::new();
workbook.add_sheet("Sheet").unwrap();
let err = match SheetPort::new(&mut workbook, manifest) {
Ok(_) => panic!("expected unsupported profile error"),
Err(err) => err,
};
match err {
formualizer_sheetport::SheetPortError::InvalidManifest { issues } => {
assert!(
issues
.iter()
.any(|issue| issue.path == "capabilities.profile"),
"expected profile issue, got {issues:#?}"
);
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn sheetport_rejects_struct_ref_under_core_profile() {
let manifest_yaml = r#"
spec: fio
spec_version: "0.3.0"
manifest: { id: core-structref, name: Core StructRef }
ports:
- id: input_a
dir: in
shape: scalar
location: { struct_ref: "Tbl[Col]" }
schema: { type: number }
"#;
let manifest: Manifest = Manifest::from_yaml_str(manifest_yaml).expect("manifest parses");
let mut workbook = Workbook::new();
workbook.add_sheet("Sheet").unwrap();
let err = match SheetPort::new(&mut workbook, manifest) {
Ok(_) => panic!("expected invalid manifest"),
Err(err) => err,
};
match err {
formualizer_sheetport::SheetPortError::InvalidManifest { issues } => {
assert!(
issues.iter().any(|issue| issue.path == "ports[0].location"),
"expected location issue, got {issues:#?}"
);
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn sheetport_rejects_table_selector_under_core_profile() {
let manifest_yaml = r#"
spec: fio
spec_version: "0.3.0"
manifest: { id: core-table, name: Core Table }
ports:
- id: input_t
dir: in
shape: table
location:
table:
name: Tbl
schema:
kind: table
columns:
- { name: a, type: number }
"#;
let manifest: Manifest = Manifest::from_yaml_str(manifest_yaml).expect("manifest parses");
let mut workbook = Workbook::new();
workbook.add_sheet("Sheet").unwrap();
let err = match SheetPort::new(&mut workbook, manifest) {
Ok(_) => panic!("expected invalid manifest"),
Err(err) => err,
};
match err {
formualizer_sheetport::SheetPortError::InvalidManifest { issues } => {
assert!(
issues.iter().any(|issue| issue.path == "ports[0].location"),
"expected location issue, got {issues:#?}"
);
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn sheetport_rejects_scalar_layout_selector() {
let manifest_yaml = r#"
spec: fio
spec_version: "0.3.0"
manifest: { id: core-scalar-layout, name: Core Scalar Layout }
ports:
- id: input_a
dir: in
shape: scalar
location:
layout:
sheet: Sheet
header_row: 1
anchor_col: A
terminate: first_blank_row
schema: { type: number }
"#;
let manifest: Manifest = Manifest::from_yaml_str(manifest_yaml).expect("manifest parses");
let mut workbook = Workbook::new();
workbook.add_sheet("Sheet").unwrap();
let err = match SheetPort::new(&mut workbook, manifest) {
Ok(_) => panic!("expected invalid manifest"),
Err(err) => err,
};
match err {
formualizer_sheetport::SheetPortError::InvalidManifest { issues } => {
assert!(
issues.iter().any(|issue| issue.path == "ports[0].location"),
"expected location issue, got {issues:#?}"
);
}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn sheetport_rejects_record_field_struct_ref_under_core_profile() {
let manifest_yaml = r#"
spec: fio
spec_version: "0.3.0"
manifest: { id: core-record-field, name: Core Record Field }
ports:
- id: rec
dir: in
shape: record
location: { a1: Sheet!A1:B1 }
schema:
kind: record
fields:
a:
type: number
location: { struct_ref: "Tbl[Col]" }
"#;
let manifest: Manifest = Manifest::from_yaml_str(manifest_yaml).expect("manifest parses");
let mut workbook = Workbook::new();
workbook.add_sheet("Sheet").unwrap();
let err = match SheetPort::new(&mut workbook, manifest) {
Ok(_) => panic!("expected invalid manifest"),
Err(err) => err,
};
match err {
formualizer_sheetport::SheetPortError::InvalidManifest { issues } => {
assert!(
issues
.iter()
.any(|issue| issue.path == "ports[0].schema.fields.a.location"),
"expected field location issue, got {issues:#?}"
);
}
other => panic!("unexpected error: {other:?}"),
}
}