#[cfg(all(feature = "validate", feature = "versioned"))]
mod identifier_tests {
use garde::Validate as _;
use rubo4e::identifiers::MaloId;
#[test]
fn malo_id_valid_passes_garde() {
let id = MaloId::new("51238696780").unwrap();
assert!(id.validate().is_ok());
}
#[test]
fn malo_id_invalid_rejected_at_construction() {
assert!(MaloId::new("12345").is_err());
}
}
#[cfg(all(feature = "validate", feature = "versioned"))]
mod marktlokation_tests {
use garde::Validate as _;
use rubo4e::v202501::{Adresse, Geokoordinaten, Katasteradresse, Marktlokation};
fn base() -> Marktlokation {
Marktlokation::default()
}
#[test]
fn xor_exactly_one_address_ok() {
let mut m = base();
m.lokationsadresse = Some(Adresse::default());
assert!(m.validate().is_ok());
}
#[test]
fn xor_no_address_fails() {
let m = base();
assert!(m.validate().is_err());
}
#[test]
fn xor_two_addresses_fails() {
let mut m = base();
m.lokationsadresse = Some(Adresse::default());
m.geoadresse = Some(Geokoordinaten::default());
assert!(m.validate().is_err());
}
#[test]
fn xor_kataster_ok() {
let mut m = base();
m.katasterinformation = Some(Katasteradresse::default());
assert!(m.validate().is_ok());
}
#[cfg(feature = "json")]
#[test]
fn golden_payload_with_lokationsadresse_validates_ok() {
let json = r#"{
"lokationsadresse": {
"strasse": "Musterstraße",
"hausnummer": "1",
"postleitzahl": "12345",
"ort": "Berlin",
"landescode": "DE"
}
}"#;
let m: Marktlokation = serde_json::from_str(json).expect("deserialize failed");
assert!(
m.lokationsadresse.is_some(),
"lokationsadresse should be set"
);
assert!(m.geoadresse.is_none());
assert!(m.katasterinformation.is_none());
assert!(
m.validate().is_ok(),
"validate() should be Ok with lokationsadresse set"
);
}
}
#[cfg(all(feature = "validate", feature = "versioned"))]
mod messlokation_tests {
use garde::Validate as _;
use rubo4e::v202501::{Adresse, Geokoordinaten, Messlokation};
#[test]
fn xor_messadresse_ok() {
let mut m = Messlokation::default();
m.messadresse = Some(Adresse::default());
assert!(m.validate().is_ok());
}
#[test]
fn xor_no_address_fails() {
let m = Messlokation::default();
assert!(m.validate().is_err());
}
#[test]
fn xor_two_addresses_fails() {
let mut m = Messlokation::default();
m.messadresse = Some(Adresse::default());
m.geoadresse = Some(Geokoordinaten::default());
assert!(m.validate().is_err());
}
}
#[cfg(all(feature = "validate", feature = "versioned", feature = "time"))]
mod vertrag_tests {
use garde::Validate as _;
use rubo4e::v202501::Vertrag;
use time::OffsetDateTime;
#[test]
fn valid_date_range_ok() {
let mut v = Vertrag::default();
v.vertragsbeginn = Some(OffsetDateTime::from_unix_timestamp(0).unwrap());
v.vertragsende = Some(OffsetDateTime::from_unix_timestamp(3600).unwrap());
assert!(v.validate().is_ok());
}
#[test]
fn inverted_date_range_fails() {
let mut v = Vertrag::default();
v.vertragsbeginn = Some(OffsetDateTime::from_unix_timestamp(3600).unwrap());
v.vertragsende = Some(OffsetDateTime::from_unix_timestamp(0).unwrap());
assert!(v.validate().is_err());
}
#[test]
fn one_date_missing_ok() {
let mut v = Vertrag::default();
v.vertragsbeginn = Some(OffsetDateTime::from_unix_timestamp(0).unwrap());
assert!(v.validate().is_ok());
}
}
#[cfg(all(feature = "validate", feature = "versioned", feature = "decimal"))]
mod rechnung_tests {
use garde::Validate as _;
use rubo4e::v202501::{Betrag, Rechnung};
use rust_decimal::prelude::FromStr as _;
use rust_decimal::Decimal;
fn dec(s: &str) -> Decimal {
Decimal::from_str(s).unwrap()
}
fn betrag(wert: Decimal) -> Option<Betrag> {
let mut b = Betrag::default();
b.wert = Some(wert);
Some(b)
}
#[test]
fn balanced_invoice_ok() {
let mut r = Rechnung::default();
r.gesamtnetto = betrag(dec("100.00"));
r.gesamtsteuer = betrag(dec("19.00"));
r.gesamtbrutto = betrag(dec("119.00"));
r.zu_zahlen = betrag(dec("119.00"));
assert!(r.validate().is_ok());
}
#[test]
fn netto_steuer_mismatch_fails() {
let mut r = Rechnung::default();
r.gesamtnetto = betrag(dec("100.00"));
r.gesamtsteuer = betrag(dec("19.00"));
r.gesamtbrutto = betrag(dec("120.00")); r.zu_zahlen = betrag(dec("120.00"));
assert!(r.validate().is_err());
}
#[test]
fn zu_zahlen_mismatch_fails() {
let mut r = Rechnung::default();
r.gesamtnetto = betrag(dec("100.00"));
r.gesamtsteuer = betrag(dec("19.00"));
r.gesamtbrutto = betrag(dec("119.00"));
r.zu_zahlen = betrag(dec("118.00")); assert!(r.validate().is_err());
}
#[test]
fn invoice_with_prepayment_ok() {
let mut r = Rechnung::default();
r.gesamtnetto = betrag(dec("100.00"));
r.gesamtsteuer = betrag(dec("19.00"));
r.gesamtbrutto = betrag(dec("119.00"));
r.vorausgezahlt = betrag(dec("20.00"));
r.zu_zahlen = betrag(dec("99.00"));
assert!(r.validate().is_ok());
}
}
#[cfg(all(feature = "validate", feature = "versioned"))]
mod report_errors_tests {
use garde::Validate as _;
use rubo4e::v202501::Marktlokation;
use rubo4e::validation::report_errors;
#[test]
fn report_errors_returns_structured_failures() {
let malo = Marktlokation::default(); let report = malo
.validate()
.expect_err("default Marktlokation should be invalid");
let failures = report_errors(&report);
assert!(
!failures.is_empty(),
"report_errors must return at least one failure for invalid Marktlokation"
);
for f in &failures {
assert!(!f.message.is_empty(), "failure message must not be empty");
}
let combined: String = failures
.iter()
.map(|f| f.message.as_str())
.collect::<Vec<_>>()
.join(" ");
assert!(
combined.contains("address")
|| combined.contains("adresse")
|| combined.contains("exactly one"),
"validation message should mention address constraint; got: {combined}"
);
}
#[test]
fn report_errors_empty_for_valid_type() {
use rubo4e::v202501::Adresse;
let mut malo = Marktlokation::default();
malo.lokationsadresse = Some(Adresse::default());
let failures = match malo.validate() {
Ok(()) => vec![],
Err(r) => report_errors(&r),
};
assert!(
failures.is_empty(),
"expected no failures for Marktlokation with address; got: {failures:?}"
);
}
}