katana-document-viewer 0.1.4

KatanA document viewer artifact, render evaluation, and export foundation.
Documentation
use super::test_support::{FixtureArtifacts, StaticPostprocessAdapter};
use super::*;
use std::error::Error;

const MINIMUM_ACCEPTED_REDUCTION_PERCENT_X100: i64 = 500;
const SLOW_POSTPROCESS_MILLIS: u128 = 31_000;

#[test]
fn disabled_mode_keeps_pdf_bytes_and_records_diagnostic() -> Result<(), Box<dyn Error>> {
    let fixture = FixtureArtifacts::new()?;
    let adapter = StaticPostprocessAdapter::success("karui", fixture.optimized_pdf());
    let service = ExportPostprocessEvaluationService::new(
        adapter,
        ExportPostprocessMode::Disabled,
        ExportPostprocessPolicy::v0_1_3(),
    );

    let report = service.evaluate(&fixture.request());

    assert_eq!(report.status, ExportPostprocessStatus::Skipped);
    assert_eq!(report.selected_pdf_bytes, fixture.pdf);
    assert_diagnostic(&report, "postprocess-disabled");
    Ok(())
}

#[test]
fn adapter_failure_keeps_pdf_bytes_and_records_reason() -> Result<(), Box<dyn Error>> {
    let fixture = FixtureArtifacts::new()?;
    let service = ExportPostprocessEvaluationService::new(
        StaticPostprocessAdapter::failure("karui", "engine unavailable"),
        ExportPostprocessMode::Enabled,
        ExportPostprocessPolicy::v0_1_3(),
    );

    let report = service.evaluate(&fixture.request());

    assert_eq!(report.status, ExportPostprocessStatus::Rejected);
    assert_eq!(report.selected_pdf_bytes, fixture.pdf);
    assert_diagnostic(&report, "postprocess-failed");
    Ok(())
}

#[test]
fn quality_regression_rejects_optimized_pdf() -> Result<(), Box<dyn Error>> {
    let fixture = FixtureArtifacts::new()?;
    let degraded_pdf = fixture.pdf_without_link_annotation();
    let service = ExportPostprocessEvaluationService::new(
        StaticPostprocessAdapter::success("karui", degraded_pdf),
        ExportPostprocessMode::Enabled,
        ExportPostprocessPolicy::v0_1_3(),
    );

    let report = service.evaluate(&fixture.request());

    assert_eq!(report.status, ExportPostprocessStatus::Rejected);
    assert_eq!(report.selected_pdf_bytes, fixture.pdf);
    assert_diagnostic(&report, "postprocess-quality-regressed");
    assert_diagnostic(&report, "postprocess-link-annotation-regressed");
    Ok(())
}

#[test]
fn useful_optimized_pdf_is_accepted_with_comparison_metrics() -> Result<(), Box<dyn Error>> {
    let fixture = FixtureArtifacts::with_test_metadata()?;
    let service = ExportPostprocessEvaluationService::new(
        StaticPostprocessAdapter::success("karui", FixtureArtifacts::base_pdf()),
        ExportPostprocessMode::Enabled,
        ExportPostprocessPolicy::v0_1_3(),
    );

    let report = service.evaluate(&fixture.request());

    assert_eq!(report.status, ExportPostprocessStatus::Accepted);
    assert_eq!(report.selected_pdf_bytes, FixtureArtifacts::base_pdf());
    assert!(report.metrics.size_reduction_percent_x100 >= MINIMUM_ACCEPTED_REDUCTION_PERCENT_X100);
    assert!(report.optimized_quality.is_pass());
    Ok(())
}

#[test]
fn small_size_reduction_is_not_enough_for_adoption() -> Result<(), Box<dyn Error>> {
    let fixture = FixtureArtifacts::new()?;
    let service = ExportPostprocessEvaluationService::new(
        StaticPostprocessAdapter::success("karui", fixture.pdf_with_one_byte_removed()),
        ExportPostprocessMode::Enabled,
        ExportPostprocessPolicy::v0_1_3(),
    );

    let report = service.evaluate(&fixture.request());

    assert_eq!(report.status, ExportPostprocessStatus::Rejected);
    assert_diagnostic(&report, "postprocess-size-reduction-too-small");
    Ok(())
}

#[test]
fn slow_postprocess_is_not_enough_for_adoption() -> Result<(), Box<dyn Error>> {
    let fixture = FixtureArtifacts::with_test_metadata()?;
    let adapter = StaticPostprocessAdapter::success("karui", FixtureArtifacts::base_pdf())
        .with_elapsed_millis(SLOW_POSTPROCESS_MILLIS);
    let service = ExportPostprocessEvaluationService::new(
        adapter,
        ExportPostprocessMode::Enabled,
        ExportPostprocessPolicy::v0_1_3(),
    );

    let report = service.evaluate(&fixture.request());

    assert_eq!(report.status, ExportPostprocessStatus::Rejected);
    assert_diagnostic(&report, "postprocess-too-slow");
    Ok(())
}

#[test]
fn karui_adapter_reports_public_engine_is_unavailable() {
    let adapter = KaruiPdfPostprocessAdapter;
    let result = adapter.postprocess_pdf(&PdfPostprocessInput {
        pdf: FixtureArtifacts::base_pdf().as_slice(),
    });

    assert_eq!(adapter.name(), "karui");
    assert!(result.is_err());
}

fn assert_diagnostic(report: &ExportPostprocessEvaluationReport, code: &str) {
    assert!(
        report
            .diagnostics
            .iter()
            .any(|diagnostic| diagnostic.code == code),
        "missing diagnostic {code}: {report:#?}"
    );
}