#!/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())