diff --git a/crates/automation/tests/it/action_failures.rs b/crates/automation/tests/it/action_failures.rs new file mode 100644 index 0000000000..b487103a21 --- /dev/null +++ b/crates/automation/tests/it/action_failures.rs @@ -0,0 +1,66 @@ +//! A step that fails fails its file: the file is reported with an error and nothing is written +//! for it. A step that finds nothing to do is still not a failure. + +use std::sync::Arc; + +use pdfcraft_automation::{Automation, Content}; +use pdfcraft_engine::Session; +use pdfcraft_engine::actions::{Action, Step, run_on}; +use serde_json::json; + +fn pdf() -> Vec { + Session::new().create_from_text("Original", "Original text").unwrap().as_ref().clone() +} + +fn one_step(step: Step) -> Action { + Action { name: "Custom".into(), description: String::new(), steps: vec![step], builtin: false } +} + +/// The standard fonts can't draw the character, so the watermark step fails and no file is produced. +#[test] +fn failing_watermark_step_fails_the_file_and_publishes_nothing() { + let r = run_on(&one_step(Step::AddWatermark("中".into())), "one.pdf", Arc::new(pdf()), |_, _| {}); + assert!(r.is_err(), "a failed step must fail its file: {r:?}"); +} + +/// A comment step on a document with no comments has nothing to do, which is not a failure. +#[test] +fn nothing_to_do_step_still_succeeds() { + let r = run_on(&one_step(Step::FlattenComments), "one.pdf", Arc::new(pdf()), |_, _| {}); + assert!(r.is_ok(), "a step with nothing to do is not a failure: {r:?}"); +} + +/// Through the tool: the failing file gets an error entry and no output, and the folder stays empty. +#[test] +fn action_run_marks_the_failing_file_as_error_without_output() { + let dir = tempfile::tempdir().unwrap(); + let input = dir.path().join("in"); + std::fs::create_dir_all(&input).unwrap(); + let in_file = input.join("one.pdf"); + std::fs::write(&in_file, pdf()).unwrap(); + let out = dir.path().join("out"); + let mut auto = Automation::new().with_root(dir.path()).unwrap(); + let reply = auto.call( + "action_run", + &json!({ + "paths": [in_file.to_string_lossy()], + "folder": out.to_string_lossy(), + "steps": [{ "step": "add_watermark", "arg": "中" }], + }), + ); + let report = match reply.unwrap().remove(0) { + Content::Json(value) => value, + other => panic!("expected a report: {other:?}"), + }; + let entry = &report["files"][0]; + assert!(entry["error"].is_string(), "{report}"); + assert!(entry.get("output").is_none(), "{report}"); + assert_eq!(std::fs::read_dir(&out).unwrap().count(), 0, "nothing is published for a failed file"); +} + +/// A removal step with nothing to remove keeps its existing note and succeeds. +#[test] +fn removal_step_with_nothing_to_remove_still_succeeds() { + let r = run_on(&one_step(Step::RemoveHiddenInformation), "one.pdf", Arc::new(pdf()), |_, _| {}); + assert!(r.is_ok(), "nothing to remove is not a failure: {r:?}"); +} diff --git a/crates/automation/tests/it/main.rs b/crates/automation/tests/it/main.rs index 39f2b8a30d..a3cd55623c 100644 --- a/crates/automation/tests/it/main.rs +++ b/crates/automation/tests/it/main.rs @@ -1,5 +1,6 @@ //! The crate's integration tests, linked into one test binary. +mod action_failures; mod attachment_key; mod automation; mod automation_roots; diff --git a/crates/engine/src/actions.rs b/crates/engine/src/actions.rs index 3e942f6a2f..249279e09d 100644 --- a/crates/engine/src/actions.rs +++ b/crates/engine/src/actions.rs @@ -197,14 +197,14 @@ fn apply_step(s: &mut Session, id: DocId, step: &Step, log: &mut Vec) -> return Ok(None); } }; - // Steps that find nothing to do (no comments to flatten, …) aren't failures. A removal step - // that didn't remove is: the file would be passed on as cleaned with the data still in it. + // A step that finds nothing to do is not a failure. Any other error fails the file, so nothing is + // published for it: the file would otherwise be passed on without the step it was asked to apply. let removes = matches!(step, Step::RemoveHiddenInformation | Step::Sanitize); match s.apply(id, edit) { Ok(()) => log.push(step.label().to_string()), Err(EditError::Redact(pdfcraft_redact::RedactError::NothingToApply)) if removes => log.push(format!("{}: nothing to remove", step.label())), - Err(e) if removes => return Err(e), - Err(e) => log.push(format!("{}: {e}", step.label())), + Err(EditError::Redact(pdfcraft_redact::RedactError::NothingToApply)) => log.push(format!("{}: nothing to do", step.label())), + Err(e) => return Err(e), } Ok(None) }