use super::*;
use crate::identifier::Gtin;
use super::tests::test_textile_data;
#[test]
fn product_group_data_battery_round_trip() {
let data = ProductGroupData::Battery(Box::new(BatteryData {
recycled_content_lithium_pct: Some(12.5),
rated_capacity_kwh: Some(32.0),
carbon_footprint_class: Some(CarbonFootprintClass::new("B").expect("valid label")),
carbon_footprint_class_ruleset_id: Some("test-cfb-classes".into()),
carbon_footprint_class_ruleset_version: Some("0.0.0-test".into()),
..crate::test_support::sample_battery_data()
}));
let json = serde_json::to_value(&data).unwrap();
assert_eq!(
json["productGroup"], "battery",
"product_group tag must be lowercase"
);
assert_eq!(json["batteryChemistry"], "LFP");
assert_eq!(json["gtin"], "09506000134352");
let back: ProductGroupData = serde_json::from_value(json).unwrap();
assert_eq!(data, back);
}
#[test]
fn battery_enum_wire_values_match_schema() {
let cases: &[(BatteryType, &str)] = &[
(BatteryType::Portable, "portable"),
(BatteryType::Industrial, "industrial"),
(BatteryType::Ev, "ev"),
(BatteryType::Lmt, "lmt"),
(BatteryType::Sli, "starting-lighting-ignition"),
];
for (variant, expected) in cases {
let json = serde_json::to_value(variant).unwrap();
assert_eq!(
json.as_str().unwrap(),
*expected,
"BatteryType::{variant:?} must serialise as \"{expected}\""
);
let back: BatteryType = serde_json::from_str(&format!("\"{expected}\"")).unwrap();
assert_eq!(back, *variant, "round-trip failed for \"{expected}\"");
}
let chem_cases: &[(BatteryChemistry, &str)] = &[
(BatteryChemistry::Lfp, "LFP"),
(BatteryChemistry::Nmc, "NMC"),
(BatteryChemistry::Nca, "NCA"),
(BatteryChemistry::Lco, "LCO"),
(BatteryChemistry::NiMh, "NiMH"),
(BatteryChemistry::NiCd, "NiCd"),
(BatteryChemistry::LeadAcid, "lead-acid"),
(BatteryChemistry::SolidState, "solid-state"),
];
for (variant, expected) in chem_cases {
let json = serde_json::to_value(variant).unwrap();
assert_eq!(
json.as_str().unwrap(),
*expected,
"BatteryChemistry::{variant:?} must serialise as \"{expected}\""
);
}
}
#[test]
fn unrecognised_battery_type_is_rejected_not_flattened() {
for bad in ["\"stationary\"", "\"Portable\"", "\"\"", "null"] {
assert!(
serde_json::from_str::<BatteryType>(bad).is_err(),
"should reject {bad}"
);
}
}
#[test]
fn device_type_wire_values_round_trip() {
let cases: &[(DeviceType, &str)] = &[
(DeviceType::Smartphone, "smartphone"),
(DeviceType::OtherMobilePhone, "other-mobile-phone"),
(DeviceType::CordlessPhone, "cordless-phone"),
(DeviceType::Tablet, "tablet"),
];
for (variant, expected) in cases {
let json = serde_json::to_value(variant).unwrap();
assert_eq!(
json.as_str().unwrap(),
*expected,
"DeviceType::{variant:?} must serialise as \"{expected}\""
);
let back: DeviceType = serde_json::from_str(&format!("\"{expected}\"")).unwrap();
assert_eq!(back, *variant, "round-trip failed for \"{expected}\"");
}
}
#[test]
fn unrecognised_device_type_is_rejected() {
for bad in ["\"laptop\"", "\"tv\"", "\"Smartphone\"", "\"\"", "null"] {
assert!(
serde_json::from_str::<DeviceType>(bad).is_err(),
"should reject {bad}"
);
}
}
#[test]
fn product_group_data_textile_round_trip() {
let mut data = test_textile_data();
data.fibre_composition = vec![FibreEntry {
fibre: "cotton".into(),
pct: 100.0,
country_of_origin: Some("IN".into()),
}];
data.repair_score = Some(6.0);
data.carbon_footprint_kg_co2e = Some(8.5);
data.country_of_raw_material_origin = Some("IN".into());
data.durability_score = Some(7.5);
data.microplastic_shedding_mg_per_wash = Some(12.3);
data.svhc_substances = Some(vec![SvhcSubstance {
cas_number: "80-05-7".into(),
substance_name: "Bisphenol A".into(),
concentration_pct: 0.15,
location_in_product: Some("coating".into()),
scip_notification_id: Some("SCIP-12345".into()),
}]);
let product_group = ProductGroupData::Textile(Box::new(data.clone()));
let json = serde_json::to_value(&product_group).unwrap();
assert_eq!(
json["productGroup"], "textile",
"product_group tag must be lowercase"
);
assert_eq!(json["countryOfOrigin"], "BD");
assert_eq!(json["durabilityScore"], 7.5);
assert_eq!(json["microplasticSheddingMgPerWash"], 12.3);
assert!(json["svhcSubstances"].is_array());
assert_eq!(json["svhcSubstances"][0]["casNumber"], "80-05-7");
assert_eq!(
json["fibreComposition"][0]["countryOfOrigin"], "IN",
"per-fibre origin must serialize"
);
let back: ProductGroupData = serde_json::from_value(json).unwrap();
assert_eq!(ProductGroupData::Textile(Box::new(data)), back);
}
#[test]
fn textile_none_fields_not_serialized() {
let data = ProductGroupData::Textile(Box::new(test_textile_data()));
let json = serde_json::to_value(&data).unwrap();
assert!(
json.get("svhcSubstances").is_none(),
"None svhc should be absent"
);
assert!(
json.get("durabilityScore").is_none(),
"None durability should be absent"
);
assert!(json.get("disassemblyInstructions").is_none());
assert!(json.get("microplasticSheddingMgPerWash").is_none());
}
#[test]
fn textile_v1_data_deserializes_with_defaults() {
let v1_json = serde_json::json!({
"productGroup": "textile",
"gtin": "09506000134352",
"fibreComposition": [{"fibre": "cotton", "pct": 100.0}],
"countryOfOrigin": "PT",
"careInstructions": "Hand wash",
"chemicalComplianceStandard": "REACH"
});
let parsed: ProductGroupData = serde_json::from_value(v1_json).unwrap();
if let ProductGroupData::Textile(t) = parsed {
assert_eq!(t.country_of_origin, "PT");
assert!(t.svhc_substances.is_none());
assert!(t.durability_score.is_none());
assert!(t.microplastic_shedding_mg_per_wash.is_none());
assert!(t.fibre_composition[0].country_of_origin.is_none());
} else {
panic!("expected Textile variant");
}
}
#[test]
fn every_product_group_declares_a_catalog_key() {
let all = [
(ProductGroup::Battery, "battery"),
(ProductGroup::Textile, "textile"),
(ProductGroup::UnsoldGoods, "unsold-goods"),
(ProductGroup::Steel, "steel"),
(ProductGroup::Electronics, "electronics"),
(ProductGroup::Construction, "construction"),
(ProductGroup::Tyre, "tyre"),
(ProductGroup::Toy, "toy"),
(ProductGroup::Aluminium, "aluminium"),
(ProductGroup::Furniture, "furniture"),
(ProductGroup::Detergent, "detergent"),
(ProductGroup::Other("other".into()), "other"),
];
for (product_group, key) in all {
assert_eq!(product_group.catalog_key(), key);
}
}
#[test]
fn product_group_discriminant_matches_variant() {
let battery = ProductGroupData::Battery(Box::new(BatteryData {
gtin: Gtin::parse("00000000000000").unwrap(),
battery_chemistry: BatteryChemistry::Nmc,
nominal_voltage_v: 4.0,
nominal_capacity_ah: 50.0,
expected_lifetime_cycles: Some(1000),
co2e_per_unit_kg: 40.0,
..crate::test_support::sample_battery_data()
}));
assert_eq!(battery.product_group(), ProductGroup::Battery);
}