"""Tests for the constraint resolver engine.""" from physcom.engine.constraint_resolver import ConstraintResolver from physcom.models.combination import Combination from physcom.models.domain import DomainConstraint from physcom.models.entity import Entity, Dependency def test_compatible_ground_combo(bicycle, human_pedalling, food_calories): """Bicycle + Human Pedalling + Food/Calories should be valid.""" resolver = ConstraintResolver() combo = Combination(entities=[bicycle, human_pedalling, food_calories]) result = resolver.resolve(combo) assert result.status != "p1_fail", f"Unexpected block: {result.violations}" def test_solar_sail_blocks_with_road_vehicle(road_vehicle, solar_sail, solar_radiation): """Road Vehicle (ground) + Solar Sail (space) should be blocked by medium mutex.""" resolver = ConstraintResolver() combo = Combination(entities=[road_vehicle, solar_sail, solar_radiation]) result = resolver.resolve(combo) assert result.status == "p1_fail" assert any("mutually exclusive" in v for v in result.violations) def test_spaceship_compatible_with_solar_sail(spaceship, solar_sail, solar_radiation): """Spaceship + Solar Sail both need space/vacuum — should not conflict.""" resolver = ConstraintResolver() combo = Combination(entities=[spaceship, solar_sail, solar_radiation]) result = resolver.resolve(combo) # Should not be blocked by atmosphere or medium medium_blocks = [v for v in result.violations if "mutually exclusive" in v] assert len(medium_blocks) == 0 def test_nuclear_drive_blocks_with_bicycle(bicycle, nuclear_thermal_drive, nuclear_fuel): """Nuclear drive min_mass=1500kg + fuel min_mass=500kg vs bicycle max_mass=30kg → range incompatibility.""" resolver = ConstraintResolver() combo = Combination(entities=[bicycle, nuclear_thermal_drive, nuclear_fuel]) result = resolver.resolve(combo) assert result.status == "p1_fail" assert any("mass" in v.lower() for v in result.violations) def test_requires_vs_excludes(): """Direct requires/excludes contradiction.""" a = Entity( name="A", dimension="platform", dependencies=[Dependency("environment", "oxygen", "true", None, "requires")], ) b = Entity( name="B", dimension="actuator", dependencies=[Dependency("environment", "oxygen", "true", None, "excludes")], ) resolver = ConstraintResolver() combo = Combination(entities=[a, b]) result = resolver.resolve(combo) assert result.status == "p1_fail" assert any("excludes" in v for v in result.violations) def test_ice_engine_blocks_with_spaceship(spaceship, ice_engine, gasoline): """ICE requires standard atmosphere, spaceship requires vacuum_or_thin → mutex.""" resolver = ConstraintResolver() combo = Combination(entities=[spaceship, ice_engine, gasoline]) result = resolver.resolve(combo) assert result.status == "p1_fail" assert any("atmosphere" in v for v in result.violations) def test_hydrogen_bicycle_valid(bicycle, hydrogen_engine, hydrogen): """Hydrogen bike — the README's example of a plausible novel concept.""" resolver = ConstraintResolver() combo = Combination(entities=[bicycle, hydrogen_engine, hydrogen]) result = resolver.resolve(combo) # Should pass constraints (mass range is compatible: h2 engine min 25kg, bike max 30kg) # This is actually a borderline case — let's just verify no hard physics blocks range_blocks = [v for v in result.violations if "mutually exclusive" in v or "atmosphere" in v] assert len(range_blocks) == 0 def test_energy_density_deficit_blocks(): """A platform needing 7200000 J/kg paired with a 720000 J/kg battery → blocked.""" platform = Entity( name="Spaceship", dimension="platform", dependencies=[ Dependency("physical", "energy_density", "7200000", "J/kg", "range_min"), ], ) storage = Entity( name="Battery", dimension="energy_storage", dependencies=[ Dependency("physical", "energy_density", "720000", "J/kg", "provides"), ], ) resolver = ConstraintResolver() combo = Combination(entities=[platform, storage]) result = resolver.resolve(combo) assert result.status == "p1_fail" assert any("deficit" in v for v in result.violations) def test_energy_density_under_density_warning(): """A platform needing 1440000 J/kg paired with a 720000 J/kg battery → conditional.""" platform = Entity( name="Airplane", dimension="platform", dependencies=[ Dependency("physical", "energy_density", "1440000", "J/kg", "range_min"), ], ) storage = Entity( name="Battery", dimension="energy_storage", dependencies=[ Dependency("physical", "energy_density", "720000", "J/kg", "provides"), ], ) resolver = ConstraintResolver() combo = Combination(entities=[platform, storage]) result = resolver.resolve(combo) assert result.status != "p1_fail" assert any("under-provision" in w for w in result.warnings) def test_energy_density_no_constraint_if_no_provider(): """A platform with energy density requirement but no declared provider → no violation.""" platform = Entity( name="Spaceship", dimension="platform", dependencies=[ Dependency("physical", "energy_density", "7200000", "J/kg", "range_min"), ], ) # Solar Sail-style: no energy_density declared actuator = Entity( name="Solar Sail", dimension="actuator", dependencies=[ Dependency("force", "power_density", "0.01", "W/kg", "provides"), ], ) resolver = ConstraintResolver() combo = Combination(entities=[platform, actuator]) result = resolver.resolve(combo) density_violations = [v for v in result.violations if "energy_density" in v] assert len(density_violations) == 0 def test_domain_constraint_blocks_wrong_medium(spaceship, solar_sail, solar_radiation): """Spaceship (space medium) should be blocked in a ground-only domain.""" resolver = ConstraintResolver() combo = Combination(entities=[spaceship, solar_sail, solar_radiation]) constraints = [DomainConstraint("medium", ["ground", "air"])] result = resolver.check_domain_constraints(combo, constraints) assert result.status == "p1_fail" assert any("medium" in v for v in result.violations) def test_domain_constraint_allows_matching_medium(bicycle, human_pedalling, food_calories): """Bicycle (ground medium) should pass a ground+air domain constraint.""" resolver = ConstraintResolver() combo = Combination(entities=[bicycle, human_pedalling, food_calories]) constraints = [DomainConstraint("medium", ["ground", "air"])] result = resolver.check_domain_constraints(combo, constraints) assert result.status == "valid"