use crate::brain::tools::doc_gen::pptx::{PptxStyle, SlideSpec, write_deck};
use serde_json::json;
fn slides(v: serde_json::Value) -> Vec<SlideSpec> {
serde_json::from_value(v).expect("valid slide specs")
}
fn host_has_python_pptx() -> bool {
std::process::Command::new("python3")
.args(["-c", "import pptx"])
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
#[tokio::test]
async fn deck_generates_when_python_pptx_present() {
if !host_has_python_pptx() {
eprintln!("skipping: python3/python-pptx not available on this host");
return;
}
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("deck.pptx");
let summary = write_deck(
&path,
&slides(json!([
{"title": "Kickoff", "bullets": ["goal one", "goal two"], "notes": "greet everyone"},
{"title": "Roadmap"}
])),
&PptxStyle::default(),
)
.await
.expect("deck written");
assert!(summary.contains("2 slide(s)"));
assert!(path.exists());
let file = std::fs::File::open(&path).expect("pptx opens");
let mut archive = zip::ZipArchive::new(file).expect("pptx is a zip");
let mut xml = String::new();
use std::io::Read;
archive
.by_name("ppt/slides/slide1.xml")
.expect("slide present")
.read_to_string(&mut xml)
.expect("slide xml reads");
assert!(xml.contains("Kickoff"));
assert!(xml.contains("goal one"));
}
#[tokio::test]
async fn missing_python_pptx_reports_actionable_error() {
if host_has_python_pptx() {
eprintln!("skipping: python-pptx IS available, cannot exercise the missing-module path");
return;
}
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("deck.pptx");
let err = write_deck(
&path,
&slides(json!([{"title": "T"}])),
&PptxStyle::default(),
)
.await
.expect_err("must fail without python-pptx");
assert!(
err.contains("python-pptx"),
"error must name the missing dependency: {err}"
);
assert!(
err.contains("pip3 install python-pptx"),
"error must tell the user how to fix it: {err}"
);
}
#[test]
fn slide_titles_with_quotes_serialize_safely() {
let specs = slides(json!([
{"title": "He said \"hello\"; import os", "bullets": ["'); print('x"]}
]));
let payload = serde_json::json!({ "slides": specs }).to_string();
let parsed: serde_json::Value = serde_json::from_str(&payload).expect("round trips");
assert_eq!(parsed["slides"][0]["title"], "He said \"hello\"; import os");
}
#[tokio::test]
async fn template_and_accent_apply_when_python_pptx_present() {
if !host_has_python_pptx() {
eprintln!("skipping: python3/python-pptx not available on this host");
return;
}
let dir = tempfile::tempdir().expect("tempdir");
let template = dir.path().join("template.pptx");
write_deck(
&template,
&slides(json!([{"title": "Template seed"}])),
&PptxStyle::default(),
)
.await
.expect("template deck written");
let path = dir.path().join("branded.pptx");
let style: PptxStyle = serde_json::from_value(json!({
"template_path": template.to_string_lossy(),
"accent_color": "#0A84FF"
}))
.expect("style parses");
let summary = write_deck(
&path,
&slides(json!([{"title": "Branded slide", "bullets": ["point"]}])),
&style,
)
.await
.expect("branded deck written");
assert!(summary.contains("1 slide(s)"));
assert!(path.exists());
}
#[tokio::test]
async fn missing_template_errors_before_spawning_python() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("deck.pptx");
let style: PptxStyle = serde_json::from_value(json!({
"template_path": "/nonexistent/brand.pptx"
}))
.expect("style parses");
let err = write_deck(&path, &slides(json!([{"title": "T"}])), &style)
.await
.expect_err("missing template must error");
assert!(err.contains("Template not found"), "err: {err}");
}