use crate::brain::tools::Tool;
use crate::brain::tools::ToolExecutionContext;
use crate::brain::tools::doc_parser::DocParserTool;
use crate::brain::tools::vba_modules::{VbaModule, clip, extract, render};
use calamine::{Xlsx, open_workbook};
use rust_xlsxwriter::Workbook;
use std::path::Path;
use uuid::Uuid;
const FIXTURE: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/tests/fixtures/vbaProject.bin"
);
fn module(name: &str, source: &str) -> VbaModule {
VbaModule {
name: name.to_string(),
source: source.to_string(),
}
}
fn write_workbook(path: &Path, with_macros: bool) {
let mut workbook = Workbook::new();
let sheet = workbook.add_worksheet();
sheet.write_string(0, 0, "value").expect("write cell");
if with_macros {
workbook
.add_vba_project(FIXTURE)
.expect("attach vba project");
}
workbook.save(path).expect("save workbook");
}
#[test]
fn clip_leaves_a_short_source_alone() {
let (body, clipped) = clip("Sub Demo()\nEnd Sub", 1024);
assert_eq!(body, "Sub Demo()\nEnd Sub");
assert!(!clipped);
}
#[test]
fn clip_steps_back_to_a_char_boundary() {
let (body, clipped) = clip("ab\u{e9}cd", 3);
assert_eq!(body, "ab");
assert!(clipped);
}
#[test]
fn render_of_no_modules_is_empty() {
assert_eq!(render(&[]), "");
}
#[test]
fn render_lists_every_name_then_each_body() {
let out = render(&[
module("Module1", "Sub Alpha()\nEnd Sub"),
module("Sheet1", "Sub Beta()\nEnd Sub"),
]);
assert!(out.contains("=== VBA modules ==="));
assert!(out.contains("- Module1\n"));
assert!(out.contains("- Sheet1\n"));
assert!(out.contains("--- Module: Module1 ---"));
assert!(out.contains("Sub Alpha()"));
assert!(out.contains("Sub Beta()"));
}
#[test]
fn render_marks_a_module_it_had_to_truncate() {
let out = render(&[module("Fat", &"x".repeat(40 * 1024))]);
assert!(
out.contains("[truncated]"),
"no truncation marker: {out:.200}"
);
assert!(out.len() < 40 * 1024, "body was not clipped");
}
#[test]
fn render_stops_once_the_total_budget_is_spent() {
let big = "y".repeat(40 * 1024);
let modules: Vec<VbaModule> = (0..5).map(|i| module(&format!("M{i}"), &big)).collect();
let out = render(&modules);
assert!(
out.contains("- M4\n"),
"every module should still be listed"
);
assert!(out.contains("--- Module: M4 ---"));
assert!(out.contains("[omitted: total VBA size limit reached]"));
}
#[test]
fn a_macro_free_workbook_yields_no_modules() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("plain.xlsx");
write_workbook(&path, false);
let mut workbook: Xlsx<_> = open_workbook(&path).expect("open xlsx");
assert!(extract(&mut workbook).is_empty());
}
#[test]
fn a_macro_workbook_yields_decompressed_source() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("macros.xlsm");
write_workbook(&path, true);
let mut workbook: Xlsx<_> = open_workbook(&path).expect("open xlsm");
let modules = extract(&mut workbook);
assert!(
!modules.is_empty(),
"fixture project has no readable modules"
);
let joined: String = modules.iter().map(|m| m.source.as_str()).collect();
assert!(
joined.contains("Sub ") || joined.contains("Function "),
"no VBA procedure in extracted source: {:?}",
modules.iter().map(|m| &m.name).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn parse_document_appends_macro_source_for_xlsm() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("macros.xlsm");
write_workbook(&path, true);
let context = ToolExecutionContext::new(Uuid::new_v4());
let input = serde_json::json!({ "path": path.to_str().expect("utf8 path") });
let result = DocParserTool
.execute(input, &context)
.await
.expect("parse xlsm");
assert!(result.success);
assert!(result.output.contains("=== Sheet:"), "sheet dump missing");
assert!(
result.output.contains("=== VBA modules ==="),
"macro section missing from parse_document output"
);
}
#[tokio::test]
async fn parse_document_stays_silent_for_a_macro_free_workbook() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("plain.xlsx");
write_workbook(&path, false);
let context = ToolExecutionContext::new(Uuid::new_v4());
let input = serde_json::json!({ "path": path.to_str().expect("utf8 path") });
let result = DocParserTool
.execute(input, &context)
.await
.expect("parse xlsx");
assert!(result.success);
assert!(!result.output.contains("VBA modules"));
}