use crate::compare_images;
use crate::render::render;
use klirr_core_invoice::{
Currency, Data, ExchangeRates, ExchangeRatesMap, FetchExchangeRates, Item, L10n, Language,
ValidInput, prepare_invoice_input_data,
};
use std::path::{Path, PathBuf};
use std::{env, error::Error, io::Write, process::Command};
use tempfile::NamedTempFile;
pub fn path_to_resource(relative: impl AsRef<Path>) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(relative)
}
pub fn fixture(relative: impl AsRef<Path>) -> PathBuf {
path_to_resource("fixtures").join(relative)
}
pub fn running_in_ci() -> bool {
env::var("CI").is_ok()
}
pub fn compare_image_against_expected(
sample: Data,
input: ValidInput,
path_to_expected_image: impl AsRef<Path>,
fetcher: impl FetchExchangeRates,
) {
let new_image =
match generate_pdf_into_png_image(L10n::new(Language::EN).unwrap(), sample, input, fetcher)
{
Ok(bytes) => bytes,
Err(err) => {
eprintln!("Skipping image comparison: {err}");
return;
}
};
compare_images::_compare_image_against_expected(
new_image,
path_to_expected_image,
!running_in_ci(),
);
}
#[cfg(test)]
#[derive(derive_more::From, Default)]
pub struct MockedExchangeRatesFetcher(ExchangeRatesMap);
#[cfg(test)]
impl FetchExchangeRates for MockedExchangeRatesFetcher {
fn fetch_for_items(
&self,
target_currency: Currency,
_items: Vec<Item>,
) -> klirr_core_invoice::Result<ExchangeRates> {
Ok(ExchangeRates::builder()
.rates(self.0.clone())
.target_currency(target_currency)
.build())
}
}
fn generate_pdf_into_png_image(
l10n: L10n,
sample: Data,
input: ValidInput,
fetcher: impl FetchExchangeRates,
) -> Result<Vec<u8>, Box<dyn Error>> {
let layout = *input.layout();
let data = prepare_invoice_input_data(sample, input, fetcher).unwrap();
let pdf = render(l10n, data, layout, |e| panic!("Got unexpected error: {e}")).unwrap();
convert_pdf_to_pngs(pdf.as_ref(), 85.0)
}
fn convert_pdf_to_pngs(pdf_bytes: &[u8], dpi: f64) -> Result<Vec<u8>, Box<dyn Error>> {
let mut temp_pdf = NamedTempFile::new()?;
temp_pdf.write_all(pdf_bytes)?;
let pdf_path = temp_pdf.path();
let temp_png = NamedTempFile::new()?;
let png_path = temp_png.path().with_extension("png");
let dpi_arg = format!("{}", dpi as u32);
let status = Command::new("magick")
.arg("-density")
.arg(&dpi_arg)
.arg("-units")
.arg("PixelsPerInch")
.arg(pdf_path)
.arg("-colorspace")
.arg("RGB")
.arg("-background")
.arg("white")
.arg("-alpha")
.arg("remove")
.arg("-flatten")
.arg("+profile")
.arg("*")
.arg("-strip")
.arg("-define")
.arg("png:compression-filter=0")
.arg("-define")
.arg("png:compression-level=9")
.arg("-define")
.arg("png:compression-strategy=1")
.arg(&png_path)
.status()?;
if !status.success() {
return Err("ImageMagick convert command failed".into());
}
let png_bytes = std::fs::read(&png_path)?;
Ok(png_bytes)
}