//! Integration tests for the `cph-check` orchestrator. //! //! These exercise phase gating and the public API against the shared `mini` //! fixture (`crates/cph-typst/tests/fixtures/mini/`) and small throwaway //! fixtures built in a temp dir. The typst Engine is pointed at the repo's real //! `render/` package via `Engine::with_render_dir`. use std::path::PathBuf; use cph_diag::DiagCode; use cph_typst::Engine; /// `/../cph-typst/tests/fixtures/mini` — the known-good lesson. fn mini_fixture() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("..") .join("cph-typst") .join("tests") .join("fixtures") .join("mini") } /// `/../../render` — the repo render package. fn render_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("..") .join("..") .join("render") } fn engine() -> Engine { Engine::with_render_dir(render_dir()) } #[test] fn good_fixture_has_no_errors() { let report = cph_check::check(&mini_fixture(), &engine()); assert!(report.lesson_loaded, "mini fixture should load"); assert_eq!( report.error_count(), 0, "mini fixture should have zero errors, got: {:#?}", report.diagnostics ); assert!(!report.has_errors()); } #[test] fn unknown_kind_is_an_error() { // Build a throwaway lesson whose part declares kind "frob". let tmp = tempdir(); std::fs::write( tmp.join("manifest.toml"), r#" [project] id = "broken" name = "broken" [info] title = "broken" [[parts]] kind = "frob" path = "elements/widget" "#, ) .unwrap(); let part_dir = tmp.join("elements").join("widget"); std::fs::create_dir_all(&part_dir).unwrap(); std::fs::write(part_dir.join("element.toml"), "kind = \"frob\"\n").unwrap(); let report = cph_check::check(&tmp, &engine()); assert!(report.lesson_loaded); assert!(report.has_errors()); assert!( report .diagnostics .iter() .any(|d| d.code == DiagCode::UnknownKind && d.message.contains("frob")), "expected an UnknownKind error mentioning 'frob', got: {:#?}", report.diagnostics ); cleanup(&tmp); } #[test] fn hard_manifest_failure_sets_lesson_not_loaded() { let tmp = tempdir(); // No manifest.toml at all → cph_model::load returns None. let report = cph_check::check(&tmp, &engine()); assert!(!report.lesson_loaded, "missing manifest is a hard failure"); assert!(report.has_errors()); cleanup(&tmp); } #[test] fn build_returns_pdf_on_good_fixture() { let (pdf, report) = cph_check::build(&mini_fixture(), &engine(), "student"); assert!(report.lesson_loaded); assert_eq!( report.error_count(), 0, "no check errors expected, got: {:#?}", report.diagnostics ); let pdf = pdf.expect("expected PDF bytes for the good fixture"); assert!(pdf.starts_with(b"%PDF"), "output should be a PDF"); assert!(pdf.len() > 1000, "PDF should be non-trivial"); } #[test] fn build_returns_none_on_broken_fixture() { // A part folder is missing → cph-model flags PartPathMissing (an error), // so build refuses before ever compiling. let tmp = tempdir(); std::fs::write( tmp.join("manifest.toml"), r#" [project] id = "broken" name = "broken" [info] title = "broken" [[parts]] kind = "segment" path = "segments/does-not-exist" "#, ) .unwrap(); let (pdf, report) = cph_check::build(&tmp, &engine(), "student"); assert!(pdf.is_none(), "broken lesson must not produce a PDF"); assert!(report.has_errors()); cleanup(&tmp); } // --- tiny temp-dir helpers (no external dev-dep) ------------------------------ fn tempdir() -> PathBuf { let mut p = std::env::temp_dir(); let nanos = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_nanos(); p.push(format!( "cph-check-test-{nanos}-{:?}", std::thread::current().id() )); std::fs::create_dir_all(&p).unwrap(); p } fn cleanup(p: &PathBuf) { let _ = std::fs::remove_dir_all(p); }