Files
physicalCombinatorics/tests/test_constraint_resolver.py
Simonson, Andrew 8118a62242 Add Flask web UI, Docker Compose, core engine + tests
- physcom core: CLI, 5-pass pipeline, SQLite repo, 37 tests
- physcom_web: Flask app with HTMX for entity/domain/pipeline/results CRUD
- Docker Compose: web + cli services sharing a named volume for the DB
- Clean up local settings to use wildcard permissions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 13:59:53 -06:00

122 lines
4.8 KiB
Python

"""Tests for the constraint resolver engine."""
from physcom.engine.constraint_resolver import ConstraintResolver
from physcom.models.combination import Combination
from physcom.models.entity import Entity, Dependency
def test_compatible_ground_combo(bicycle, human_pedalling):
"""Bicycle + Human Pedalling should be valid."""
resolver = ConstraintResolver()
combo = Combination(entities=[bicycle, human_pedalling])
result = resolver.resolve(combo)
assert result.status != "blocked", f"Unexpected block: {result.violations}"
def test_solar_sail_blocks_with_walking(walking, solar_sail):
"""Walking (ground) + Solar Sail (space) should be blocked by medium mutex."""
resolver = ConstraintResolver()
combo = Combination(entities=[walking, solar_sail])
result = resolver.resolve(combo)
assert result.status == "blocked"
assert any("mutually exclusive" in v for v in result.violations)
def test_spaceship_compatible_with_solar_sail(spaceship, solar_sail):
"""Spaceship + Solar Sail both need space/vacuum — should not conflict."""
resolver = ConstraintResolver()
combo = Combination(entities=[spaceship, solar_sail])
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_reactor_blocks_with_bicycle(bicycle, nuclear_reactor):
"""Nuclear reactor min_mass=2000kg vs bicycle max_mass=30kg → range incompatibility."""
resolver = ConstraintResolver()
combo = Combination(entities=[bicycle, nuclear_reactor])
result = resolver.resolve(combo)
assert result.status == "blocked"
assert any("mass" in v.lower() for v in result.violations)
def test_force_scale_mismatch_blocks():
"""A platform needing 1MW and a power source providing 1W → force deficit block."""
platform = Entity(
name="HeavyPlatform", dimension="platform",
dependencies=[
Dependency("force", "force_required_watts", "1000000", "watts", "range_min"),
],
)
power = Entity(
name="TinyPower", dimension="power_source",
dependencies=[
Dependency("force", "force_output_watts", "1", "watts", "provides"),
],
)
resolver = ConstraintResolver()
combo = Combination(entities=[platform, power])
result = resolver.resolve(combo)
assert result.status == "blocked"
assert any("force deficit" in v for v in result.violations)
def test_force_under_powered_warning():
"""Power source slightly below requirement → warning, not block."""
platform = Entity(
name="MedPlatform", dimension="platform",
dependencies=[
Dependency("force", "force_required_watts", "1000", "watts", "range_min"),
],
)
power = Entity(
name="WeakPower", dimension="power_source",
dependencies=[
Dependency("force", "force_output_watts", "500", "watts", "provides"),
],
)
resolver = ConstraintResolver()
combo = Combination(entities=[platform, power])
result = resolver.resolve(combo)
# Under-powered but within 100x → warning, not block
assert result.status != "blocked"
assert any("under-powered" in w for w in result.warnings)
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="power_source",
dependencies=[Dependency("environment", "oxygen", "true", None, "excludes")],
)
resolver = ConstraintResolver()
combo = Combination(entities=[a, b])
result = resolver.resolve(combo)
assert result.status == "blocked"
assert any("excludes" in v for v in result.violations)
def test_ice_engine_blocks_with_spaceship(spaceship, ice_engine):
"""ICE requires standard atmosphere, spaceship requires vacuum_or_thin → mutex."""
resolver = ConstraintResolver()
combo = Combination(entities=[spaceship, ice_engine])
result = resolver.resolve(combo)
assert result.status == "blocked"
assert any("atmosphere" in v for v in result.violations)
def test_hydrogen_bicycle_valid(bicycle, hydrogen_engine):
"""Hydrogen bike — the README's example of a plausible novel concept."""
resolver = ConstraintResolver()
combo = Combination(entities=[bicycle, hydrogen_engine])
result = resolver.resolve(combo)
# Should pass constraints (mass range is compatible: h2 min 30kg, 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