dynamic dashboard initial
This commit is contained in:
288
tools/test_layout_loader.py
Normal file
288
tools/test_layout_loader.py
Normal file
@@ -0,0 +1,288 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test layout loader and models."""
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from packages.home_capabilities.layout import DeviceTile, Room, UiLayout, load_layout
|
||||
|
||||
|
||||
def test_device_tile_creation():
|
||||
"""Test DeviceTile model creation."""
|
||||
print("Test 1: DeviceTile Creation")
|
||||
|
||||
tile = DeviceTile(
|
||||
device_id="test_lamp",
|
||||
title="Test Lamp",
|
||||
icon="💡",
|
||||
rank=10
|
||||
)
|
||||
|
||||
assert tile.device_id == "test_lamp"
|
||||
assert tile.title == "Test Lamp"
|
||||
assert tile.icon == "💡"
|
||||
assert tile.rank == 10
|
||||
|
||||
print(" ✓ DeviceTile created successfully")
|
||||
return True
|
||||
|
||||
|
||||
def test_room_creation():
|
||||
"""Test Room model creation."""
|
||||
print("\nTest 2: Room Creation")
|
||||
|
||||
tiles = [
|
||||
DeviceTile(device_id="lamp1", title="Lamp 1", icon="💡", rank=1),
|
||||
DeviceTile(device_id="lamp2", title="Lamp 2", icon="💡", rank=2)
|
||||
]
|
||||
|
||||
room = Room(name="Living Room", devices=tiles)
|
||||
|
||||
assert room.name == "Living Room"
|
||||
assert len(room.devices) == 2
|
||||
assert room.devices[0].rank == 1
|
||||
|
||||
print(" ✓ Room with 2 devices created")
|
||||
return True
|
||||
|
||||
|
||||
def test_ui_layout_creation():
|
||||
"""Test UiLayout model creation."""
|
||||
print("\nTest 3: UiLayout Creation")
|
||||
|
||||
rooms = [
|
||||
Room(
|
||||
name="Room 1",
|
||||
devices=[DeviceTile(device_id="d1", title="D1", icon="💡", rank=1)]
|
||||
),
|
||||
Room(
|
||||
name="Room 2",
|
||||
devices=[DeviceTile(device_id="d2", title="D2", icon="💡", rank=1)]
|
||||
)
|
||||
]
|
||||
|
||||
layout = UiLayout(rooms=rooms)
|
||||
|
||||
assert len(layout.rooms) == 2
|
||||
assert layout.total_devices() == 2
|
||||
|
||||
print(" ✓ UiLayout with 2 rooms created")
|
||||
print(f" ✓ Total devices: {layout.total_devices()}")
|
||||
return True
|
||||
|
||||
|
||||
def test_layout_validation_empty_rooms():
|
||||
"""Test that layout validation rejects empty rooms list."""
|
||||
print("\nTest 4: Validation - Empty Rooms")
|
||||
|
||||
try:
|
||||
UiLayout(rooms=[])
|
||||
print(" ✗ Should have raised ValueError")
|
||||
return False
|
||||
except ValueError as e:
|
||||
if "at least one room" in str(e):
|
||||
print(f" ✓ Correct validation error: {e}")
|
||||
return True
|
||||
else:
|
||||
print(f" ✗ Wrong error message: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_load_layout_from_file():
|
||||
"""Test loading layout from YAML file."""
|
||||
print("\nTest 5: Load Layout from YAML")
|
||||
|
||||
yaml_content = """
|
||||
rooms:
|
||||
- name: Wohnzimmer
|
||||
devices:
|
||||
- device_id: test_lampe_1
|
||||
title: Stehlampe
|
||||
icon: "💡"
|
||||
rank: 10
|
||||
- device_id: test_lampe_2
|
||||
title: Tischlampe
|
||||
icon: "💡"
|
||||
rank: 20
|
||||
|
||||
- name: Schlafzimmer
|
||||
devices:
|
||||
- device_id: test_lampe_3
|
||||
title: Nachttischlampe
|
||||
icon: "🛏️"
|
||||
rank: 5
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
|
||||
f.write(yaml_content)
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
layout = load_layout(str(temp_path))
|
||||
|
||||
assert len(layout.rooms) == 2
|
||||
assert layout.rooms[0].name == "Wohnzimmer"
|
||||
assert len(layout.rooms[0].devices) == 2
|
||||
assert layout.total_devices() == 3
|
||||
|
||||
# Check device sorting by rank
|
||||
wohnzimmer_devices = layout.rooms[0].devices
|
||||
assert wohnzimmer_devices[0].rank == 10
|
||||
assert wohnzimmer_devices[1].rank == 20
|
||||
|
||||
print(" ✓ Layout loaded from YAML")
|
||||
print(f" ✓ Rooms: {len(layout.rooms)}")
|
||||
print(f" ✓ Total devices: {layout.total_devices()}")
|
||||
print(f" ✓ Room order preserved: {[r.name for r in layout.rooms]}")
|
||||
|
||||
return True
|
||||
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
|
||||
def test_load_layout_missing_file():
|
||||
"""Test error handling for missing file."""
|
||||
print("\nTest 6: Missing File Error")
|
||||
|
||||
try:
|
||||
load_layout("/nonexistent/path/layout.yaml")
|
||||
print(" ✗ Should have raised FileNotFoundError")
|
||||
return False
|
||||
except FileNotFoundError as e:
|
||||
if "not found" in str(e):
|
||||
print(f" ✓ Correct error for missing file")
|
||||
return True
|
||||
else:
|
||||
print(f" ✗ Wrong error message: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_load_layout_invalid_yaml():
|
||||
"""Test error handling for invalid YAML."""
|
||||
print("\nTest 7: Invalid YAML Error")
|
||||
|
||||
yaml_content = """
|
||||
rooms:
|
||||
- name: Room1
|
||||
devices: [invalid yaml structure
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
|
||||
f.write(yaml_content)
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
load_layout(str(temp_path))
|
||||
print(" ✗ Should have raised YAMLError")
|
||||
temp_path.unlink()
|
||||
return False
|
||||
except Exception as e:
|
||||
temp_path.unlink()
|
||||
if "YAML" in str(type(e).__name__) or "parse" in str(e).lower():
|
||||
print(f" ✓ Correct YAML parsing error")
|
||||
return True
|
||||
else:
|
||||
print(f" ✗ Unexpected error: {type(e).__name__}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_load_layout_missing_required_fields():
|
||||
"""Test validation for missing required fields."""
|
||||
print("\nTest 8: Missing Required Fields")
|
||||
|
||||
yaml_content = """
|
||||
rooms:
|
||||
- name: Room1
|
||||
devices:
|
||||
- device_id: lamp1
|
||||
title: Lamp
|
||||
# Missing: icon, rank
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
|
||||
f.write(yaml_content)
|
||||
temp_path = Path(f.name)
|
||||
|
||||
try:
|
||||
load_layout(str(temp_path))
|
||||
print(" ✗ Should have raised ValueError")
|
||||
temp_path.unlink()
|
||||
return False
|
||||
except ValueError as e:
|
||||
temp_path.unlink()
|
||||
if "icon" in str(e) or "rank" in str(e) or "required" in str(e).lower():
|
||||
print(f" ✓ Validation error for missing fields")
|
||||
return True
|
||||
else:
|
||||
print(f" ✗ Wrong error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_load_default_layout():
|
||||
"""Test loading default layout from workspace."""
|
||||
print("\nTest 9: Load Default Layout (config/layout.yaml)")
|
||||
|
||||
try:
|
||||
layout = load_layout()
|
||||
|
||||
print(f" ✓ Default layout loaded")
|
||||
print(f" ✓ Rooms: {len(layout.rooms)} ({', '.join(r.name for r in layout.rooms)})")
|
||||
print(f" ✓ Total devices: {layout.total_devices()}")
|
||||
|
||||
for room in layout.rooms:
|
||||
print(f" - {room.name}: {len(room.devices)} devices")
|
||||
for device in room.devices:
|
||||
print(f" • {device.title} (id={device.device_id}, rank={device.rank})")
|
||||
|
||||
return True
|
||||
|
||||
except FileNotFoundError:
|
||||
print(" ⚠ Default layout.yaml not found (expected at config/layout.yaml)")
|
||||
return True # Not a failure if file doesn't exist yet
|
||||
except Exception as e:
|
||||
print(f" ✗ Error loading default layout: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all tests."""
|
||||
print("=" * 60)
|
||||
print("Testing Layout Loader and Models")
|
||||
print("=" * 60)
|
||||
|
||||
tests = [
|
||||
test_device_tile_creation,
|
||||
test_room_creation,
|
||||
test_ui_layout_creation,
|
||||
test_layout_validation_empty_rooms,
|
||||
test_load_layout_from_file,
|
||||
test_load_layout_missing_file,
|
||||
test_load_layout_invalid_yaml,
|
||||
test_load_layout_missing_required_fields,
|
||||
test_load_default_layout,
|
||||
]
|
||||
|
||||
results = []
|
||||
for test in tests:
|
||||
try:
|
||||
results.append(test())
|
||||
except Exception as e:
|
||||
print(f" ✗ Unexpected exception: {e}")
|
||||
results.append(False)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
passed = sum(results)
|
||||
total = len(results)
|
||||
print(f"Results: {passed}/{total} tests passed")
|
||||
print("=" * 60)
|
||||
|
||||
return 0 if all(results) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user