"""Tests for product configuration validation.""" import pytest # Required top-level sections REQUIRED_SECTIONS = ["product", "design", "costs", "pricing", "return_assumptions", "environmental"] # Required product fields REQUIRED_PRODUCT_FIELDS = ["name", "category", "retail_price"] # Required design fields REQUIRED_DESIGN_FIELDS = ["lifetime_years", "use_cycle_years", "max_cycles"] # Required cost fields REQUIRED_COST_FIELDS = ["manufacturing_unit_cost"] class TestConfigStructure: """Test that configs have required structure.""" def test_besafe_has_required_sections(self, besafe_config): """BeSafe config has all required sections.""" for section in REQUIRED_SECTIONS: assert section in besafe_config, f"Missing section: {section}" def test_hamax_has_required_sections(self, hamax_config): """Hamax config has all required sections.""" for section in REQUIRED_SECTIONS: assert section in hamax_config, f"Missing section: {section}" def test_all_configs_have_required_sections(self, all_configs): """All product configs have required sections.""" for name, config in all_configs.items(): for section in REQUIRED_SECTIONS: assert section in config, f"{name}: Missing section: {section}" class TestProductSection: """Test product section validation.""" def test_product_has_required_fields(self, all_configs): """Product section has required fields.""" for name, config in all_configs.items(): for field in REQUIRED_PRODUCT_FIELDS: assert field in config["product"], f"{name}: Missing product.{field}" def test_retail_price_positive(self, all_configs): """Retail price is positive.""" for name, config in all_configs.items(): price = config["product"]["retail_price"] assert price > 0, f"{name}: retail_price must be positive, got {price}" class TestDesignSection: """Test design section validation.""" def test_design_has_required_fields(self, all_configs): """Design section has required fields.""" for name, config in all_configs.items(): for field in REQUIRED_DESIGN_FIELDS: assert field in config["design"], f"{name}: Missing design.{field}" def test_lifetime_greater_than_use_cycle(self, all_configs): """Design lifetime >= use cycle years.""" for name, config in all_configs.items(): lifetime = config["design"]["lifetime_years"] use_cycle = config["design"]["use_cycle_years"] assert lifetime >= use_cycle, f"{name}: lifetime_years ({lifetime}) < use_cycle_years ({use_cycle})" def test_max_cycles_positive(self, all_configs): """Max cycles is positive.""" for name, config in all_configs.items(): cycles = config["design"]["max_cycles"] assert cycles > 0, f"{name}: max_cycles must be positive, got {cycles}" class TestCostsSection: """Test costs section validation.""" def test_manufacturing_cost_positive(self, all_configs): """Manufacturing cost is positive.""" for name, config in all_configs.items(): cost = config["costs"]["manufacturing_unit_cost"] assert cost > 0, f"{name}: manufacturing_unit_cost must be positive, got {cost}" def test_refurbishment_costs_exist(self, all_configs): """Refurbishment costs exist for grades A, B, C.""" for name, config in all_configs.items(): refurb = config["costs"].get("refurbishment", {}) assert "grade_a" in refurb, f"{name}: Missing refurbishment.grade_a" assert "grade_b" in refurb, f"{name}: Missing refurbishment.grade_b" def test_refurb_costs_ascending(self, all_configs): """Refurbishment costs: grade_a <= grade_b.""" for name, config in all_configs.items(): refurb = config["costs"]["refurbishment"] assert refurb["grade_a"] <= refurb["grade_b"], \ f"{name}: grade_a cost ({refurb['grade_a']}) > grade_b ({refurb['grade_b']})" class TestReturnAssumptions: """Test return assumptions validation.""" def test_return_rate_valid_range(self, all_configs): """Return rate is between 0 and 1.""" for name, config in all_configs.items(): rate = config["return_assumptions"]["return_rate"] assert 0 <= rate <= 1, f"{name}: return_rate must be 0-1, got {rate}" def test_grade_distribution_sums_to_one(self, all_configs): """Grade distribution (a + b + c) sums to 1.""" for name, config in all_configs.items(): dist = config["return_assumptions"]["grade_distribution"] total = dist.get("a", 0) + dist.get("b", 0) + dist.get("c", 0) assert abs(total - 1.0) < 0.01, f"{name}: grade distribution sums to {total}, not 1.0" class TestMaterialsBOM: """Test materials BOM validation (if present).""" def test_bom_weights_positive(self, all_configs): """BOM component weights are positive.""" for name, config in all_configs.items(): if "materials" in config and "bom" in config["materials"]: for comp_name, comp in config["materials"]["bom"].items(): weight = comp.get("weight_kg", 0) assert weight > 0, f"{name}: {comp_name} weight must be positive" def test_recyclable_materials_have_recovery_value(self, all_configs): """Recyclable materials should have recovery value.""" for name, config in all_configs.items(): if "materials" in config and "bom" in config["materials"]: for comp_name, comp in config["materials"]["bom"].items(): if comp.get("recyclable", False) and "virgin_cost_per_kg" in comp: assert "recovery_value_per_kg" in comp, \ f"{name}: {comp_name} is recyclable but missing recovery_value_per_kg"