forked from Rockachopa/Timmy-time-dashboard
@@ -2,22 +2,22 @@
|
|||||||
# Serves lighting, environment, and feature settings to the Matrix frontend.
|
# Serves lighting, environment, and feature settings to the Matrix frontend.
|
||||||
|
|
||||||
lighting:
|
lighting:
|
||||||
ambient_color: "#1a1a2e"
|
ambient_color: "#FFAA55" # Warm amber (Workshop warmth)
|
||||||
ambient_intensity: 0.4
|
ambient_intensity: 0.5
|
||||||
point_lights:
|
point_lights:
|
||||||
- color: "#FFD700"
|
- color: "#FFAA55" # Warm amber (Workshop center light)
|
||||||
intensity: 1.2
|
intensity: 1.2
|
||||||
position: { x: 0, y: 5, z: 0 }
|
position: { x: 0, y: 5, z: 0 }
|
||||||
- color: "#3B82F6"
|
- color: "#3B82F6" # Cool blue (Matrix accent)
|
||||||
intensity: 0.8
|
intensity: 0.8
|
||||||
position: { x: -5, y: 3, z: -5 }
|
position: { x: -5, y: 3, z: -5 }
|
||||||
- color: "#A855F7"
|
- color: "#A855F7" # Purple accent
|
||||||
intensity: 0.6
|
intensity: 0.6
|
||||||
position: { x: 5, y: 3, z: 5 }
|
position: { x: 5, y: 3, z: 5 }
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
rain_enabled: false
|
rain_enabled: false
|
||||||
starfield_enabled: true
|
starfield_enabled: true # Cool blue starfield (Matrix feel)
|
||||||
fog_color: "#0f0f23"
|
fog_color: "#0f0f23"
|
||||||
fog_density: 0.02
|
fog_density: 0.02
|
||||||
|
|
||||||
@@ -26,3 +26,8 @@ features:
|
|||||||
visitor_avatars: true
|
visitor_avatars: true
|
||||||
pip_familiar: true
|
pip_familiar: true
|
||||||
workshop_portal: true
|
workshop_portal: true
|
||||||
|
|
||||||
|
agents:
|
||||||
|
default_count: 5
|
||||||
|
max_count: 20
|
||||||
|
agents: []
|
||||||
|
|||||||
266
src/infrastructure/matrix_config.py
Normal file
266
src/infrastructure/matrix_config.py
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
"""Matrix configuration loader utility.
|
||||||
|
|
||||||
|
Provides a typed dataclass for Matrix world configuration and a loader
|
||||||
|
that fetches settings from YAML with sensible defaults.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PointLight:
|
||||||
|
"""A single point light in the Matrix world."""
|
||||||
|
|
||||||
|
color: str = "#FFFFFF"
|
||||||
|
intensity: float = 1.0
|
||||||
|
position: dict[str, float] = field(default_factory=lambda: {"x": 0, "y": 0, "z": 0})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict[str, Any]) -> "PointLight":
|
||||||
|
"""Create a PointLight from a dictionary with defaults."""
|
||||||
|
return cls(
|
||||||
|
color=data.get("color", "#FFFFFF"),
|
||||||
|
intensity=data.get("intensity", 1.0),
|
||||||
|
position=data.get("position", {"x": 0, "y": 0, "z": 0}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _default_point_lights_factory() -> list[PointLight]:
|
||||||
|
"""Factory function for default point lights."""
|
||||||
|
return [
|
||||||
|
PointLight(
|
||||||
|
color="#FFAA55", # Warm amber (Workshop)
|
||||||
|
intensity=1.2,
|
||||||
|
position={"x": 0, "y": 5, "z": 0},
|
||||||
|
),
|
||||||
|
PointLight(
|
||||||
|
color="#3B82F6", # Cool blue (Matrix)
|
||||||
|
intensity=0.8,
|
||||||
|
position={"x": -5, "y": 3, "z": -5},
|
||||||
|
),
|
||||||
|
PointLight(
|
||||||
|
color="#A855F7", # Purple accent
|
||||||
|
intensity=0.6,
|
||||||
|
position={"x": 5, "y": 3, "z": 5},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LightingConfig:
|
||||||
|
"""Lighting configuration for the Matrix world."""
|
||||||
|
|
||||||
|
ambient_color: str = "#FFAA55" # Warm amber (Workshop warmth)
|
||||||
|
ambient_intensity: float = 0.5
|
||||||
|
point_lights: list[PointLight] = field(default_factory=_default_point_lights_factory)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict[str, Any] | None) -> "LightingConfig":
|
||||||
|
"""Create a LightingConfig from a dictionary with defaults."""
|
||||||
|
if data is None:
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
point_lights_data = data.get("point_lights", [])
|
||||||
|
point_lights = (
|
||||||
|
[PointLight.from_dict(pl) for pl in point_lights_data]
|
||||||
|
if point_lights_data
|
||||||
|
else _default_point_lights_factory()
|
||||||
|
)
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
ambient_color=data.get("ambient_color", "#FFAA55"),
|
||||||
|
ambient_intensity=data.get("ambient_intensity", 0.5),
|
||||||
|
point_lights=point_lights,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EnvironmentConfig:
|
||||||
|
"""Environment settings for the Matrix world."""
|
||||||
|
|
||||||
|
rain_enabled: bool = False
|
||||||
|
starfield_enabled: bool = True
|
||||||
|
fog_color: str = "#0f0f23"
|
||||||
|
fog_density: float = 0.02
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict[str, Any] | None) -> "EnvironmentConfig":
|
||||||
|
"""Create an EnvironmentConfig from a dictionary with defaults."""
|
||||||
|
if data is None:
|
||||||
|
data = {}
|
||||||
|
return cls(
|
||||||
|
rain_enabled=data.get("rain_enabled", False),
|
||||||
|
starfield_enabled=data.get("starfield_enabled", True),
|
||||||
|
fog_color=data.get("fog_color", "#0f0f23"),
|
||||||
|
fog_density=data.get("fog_density", 0.02),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FeaturesConfig:
|
||||||
|
"""Feature toggles for the Matrix world."""
|
||||||
|
|
||||||
|
chat_enabled: bool = True
|
||||||
|
visitor_avatars: bool = True
|
||||||
|
pip_familiar: bool = True
|
||||||
|
workshop_portal: bool = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict[str, Any] | None) -> "FeaturesConfig":
|
||||||
|
"""Create a FeaturesConfig from a dictionary with defaults."""
|
||||||
|
if data is None:
|
||||||
|
data = {}
|
||||||
|
return cls(
|
||||||
|
chat_enabled=data.get("chat_enabled", True),
|
||||||
|
visitor_avatars=data.get("visitor_avatars", True),
|
||||||
|
pip_familiar=data.get("pip_familiar", True),
|
||||||
|
workshop_portal=data.get("workshop_portal", True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AgentConfig:
|
||||||
|
"""Configuration for a single Matrix agent."""
|
||||||
|
|
||||||
|
name: str = ""
|
||||||
|
role: str = ""
|
||||||
|
enabled: bool = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict[str, Any]) -> "AgentConfig":
|
||||||
|
"""Create an AgentConfig from a dictionary with defaults."""
|
||||||
|
return cls(
|
||||||
|
name=data.get("name", ""),
|
||||||
|
role=data.get("role", ""),
|
||||||
|
enabled=data.get("enabled", True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AgentsConfig:
|
||||||
|
"""Agent registry configuration."""
|
||||||
|
|
||||||
|
default_count: int = 5
|
||||||
|
max_count: int = 20
|
||||||
|
agents: list[AgentConfig] = field(default_factory=list)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict[str, Any] | None) -> "AgentsConfig":
|
||||||
|
"""Create an AgentsConfig from a dictionary with defaults."""
|
||||||
|
if data is None:
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
agents_data = data.get("agents", [])
|
||||||
|
agents = [AgentConfig.from_dict(a) for a in agents_data] if agents_data else []
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
default_count=data.get("default_count", 5),
|
||||||
|
max_count=data.get("max_count", 20),
|
||||||
|
agents=agents,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MatrixConfig:
|
||||||
|
"""Complete Matrix world configuration.
|
||||||
|
|
||||||
|
Combines lighting, environment, features, and agent settings
|
||||||
|
into a single configuration object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
lighting: LightingConfig = field(default_factory=LightingConfig)
|
||||||
|
environment: EnvironmentConfig = field(default_factory=EnvironmentConfig)
|
||||||
|
features: FeaturesConfig = field(default_factory=FeaturesConfig)
|
||||||
|
agents: AgentsConfig = field(default_factory=AgentsConfig)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict[str, Any] | None) -> "MatrixConfig":
|
||||||
|
"""Create a MatrixConfig from a dictionary with defaults for missing sections."""
|
||||||
|
if data is None:
|
||||||
|
data = {}
|
||||||
|
return cls(
|
||||||
|
lighting=LightingConfig.from_dict(data.get("lighting")),
|
||||||
|
environment=EnvironmentConfig.from_dict(data.get("environment")),
|
||||||
|
features=FeaturesConfig.from_dict(data.get("features")),
|
||||||
|
agents=AgentsConfig.from_dict(data.get("agents")),
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
"""Convert the configuration to a plain dictionary."""
|
||||||
|
return {
|
||||||
|
"lighting": {
|
||||||
|
"ambient_color": self.lighting.ambient_color,
|
||||||
|
"ambient_intensity": self.lighting.ambient_intensity,
|
||||||
|
"point_lights": [
|
||||||
|
{
|
||||||
|
"color": pl.color,
|
||||||
|
"intensity": pl.intensity,
|
||||||
|
"position": pl.position,
|
||||||
|
}
|
||||||
|
for pl in self.lighting.point_lights
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"rain_enabled": self.environment.rain_enabled,
|
||||||
|
"starfield_enabled": self.environment.starfield_enabled,
|
||||||
|
"fog_color": self.environment.fog_color,
|
||||||
|
"fog_density": self.environment.fog_density,
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"chat_enabled": self.features.chat_enabled,
|
||||||
|
"visitor_avatars": self.features.visitor_avatars,
|
||||||
|
"pip_familiar": self.features.pip_familiar,
|
||||||
|
"workshop_portal": self.features.workshop_portal,
|
||||||
|
},
|
||||||
|
"agents": {
|
||||||
|
"default_count": self.agents.default_count,
|
||||||
|
"max_count": self.agents.max_count,
|
||||||
|
"agents": [
|
||||||
|
{"name": a.name, "role": a.role, "enabled": a.enabled}
|
||||||
|
for a in self.agents.agents
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_from_yaml(path: str | Path) -> MatrixConfig:
|
||||||
|
"""Load Matrix configuration from a YAML file.
|
||||||
|
|
||||||
|
Missing keys are filled with sensible defaults. If the file
|
||||||
|
cannot be read or parsed, returns a fully default configuration.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: Path to the YAML configuration file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A MatrixConfig instance with loaded or default values.
|
||||||
|
"""
|
||||||
|
path = Path(path)
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
logger.warning("Matrix config file not found: %s, using defaults", path)
|
||||||
|
return MatrixConfig()
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(path, encoding="utf-8") as f:
|
||||||
|
raw_data = yaml.safe_load(f)
|
||||||
|
|
||||||
|
if not isinstance(raw_data, dict):
|
||||||
|
logger.warning("Matrix config invalid format, using defaults")
|
||||||
|
return MatrixConfig()
|
||||||
|
|
||||||
|
return MatrixConfig.from_dict(raw_data)
|
||||||
|
|
||||||
|
except yaml.YAMLError as exc:
|
||||||
|
logger.warning("Matrix config YAML parse error: %s, using defaults", exc)
|
||||||
|
return MatrixConfig()
|
||||||
|
except OSError as exc:
|
||||||
|
logger.warning("Matrix config read error: %s, using defaults", exc)
|
||||||
|
return MatrixConfig()
|
||||||
331
tests/unit/test_matrix_config.py
Normal file
331
tests/unit/test_matrix_config.py
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
"""Tests for the matrix configuration loader utility."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from infrastructure.matrix_config import (
|
||||||
|
AgentConfig,
|
||||||
|
AgentsConfig,
|
||||||
|
EnvironmentConfig,
|
||||||
|
FeaturesConfig,
|
||||||
|
LightingConfig,
|
||||||
|
MatrixConfig,
|
||||||
|
PointLight,
|
||||||
|
load_from_yaml,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPointLight:
|
||||||
|
"""Tests for PointLight dataclass."""
|
||||||
|
|
||||||
|
def test_default_values(self):
|
||||||
|
"""PointLight has correct defaults."""
|
||||||
|
pl = PointLight()
|
||||||
|
assert pl.color == "#FFFFFF"
|
||||||
|
assert pl.intensity == 1.0
|
||||||
|
assert pl.position == {"x": 0, "y": 0, "z": 0}
|
||||||
|
|
||||||
|
def test_from_dict_full(self):
|
||||||
|
"""PointLight.from_dict loads all fields."""
|
||||||
|
data = {
|
||||||
|
"color": "#FF0000",
|
||||||
|
"intensity": 2.5,
|
||||||
|
"position": {"x": 1, "y": 2, "z": 3},
|
||||||
|
}
|
||||||
|
pl = PointLight.from_dict(data)
|
||||||
|
assert pl.color == "#FF0000"
|
||||||
|
assert pl.intensity == 2.5
|
||||||
|
assert pl.position == {"x": 1, "y": 2, "z": 3}
|
||||||
|
|
||||||
|
def test_from_dict_partial(self):
|
||||||
|
"""PointLight.from_dict fills missing fields with defaults."""
|
||||||
|
data = {"color": "#00FF00"}
|
||||||
|
pl = PointLight.from_dict(data)
|
||||||
|
assert pl.color == "#00FF00"
|
||||||
|
assert pl.intensity == 1.0
|
||||||
|
assert pl.position == {"x": 0, "y": 0, "z": 0}
|
||||||
|
|
||||||
|
|
||||||
|
class TestLightingConfig:
|
||||||
|
"""Tests for LightingConfig dataclass."""
|
||||||
|
|
||||||
|
def test_default_values(self):
|
||||||
|
"""LightingConfig has correct Workshop+Matrix blend defaults."""
|
||||||
|
cfg = LightingConfig()
|
||||||
|
assert cfg.ambient_color == "#FFAA55" # Warm amber (Workshop)
|
||||||
|
assert cfg.ambient_intensity == 0.5
|
||||||
|
assert len(cfg.point_lights) == 3
|
||||||
|
# First light is warm amber center
|
||||||
|
assert cfg.point_lights[0].color == "#FFAA55"
|
||||||
|
# Second light is cool blue (Matrix)
|
||||||
|
assert cfg.point_lights[1].color == "#3B82F6"
|
||||||
|
|
||||||
|
def test_from_dict_full(self):
|
||||||
|
"""LightingConfig.from_dict loads all fields."""
|
||||||
|
data = {
|
||||||
|
"ambient_color": "#123456",
|
||||||
|
"ambient_intensity": 0.8,
|
||||||
|
"point_lights": [
|
||||||
|
{"color": "#ABCDEF", "intensity": 1.5, "position": {"x": 1, "y": 1, "z": 1}}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
cfg = LightingConfig.from_dict(data)
|
||||||
|
assert cfg.ambient_color == "#123456"
|
||||||
|
assert cfg.ambient_intensity == 0.8
|
||||||
|
assert len(cfg.point_lights) == 1
|
||||||
|
assert cfg.point_lights[0].color == "#ABCDEF"
|
||||||
|
|
||||||
|
def test_from_dict_empty_list_uses_defaults(self):
|
||||||
|
"""Empty point_lights list triggers default lights."""
|
||||||
|
data = {"ambient_color": "#000000", "point_lights": []}
|
||||||
|
cfg = LightingConfig.from_dict(data)
|
||||||
|
assert cfg.ambient_color == "#000000"
|
||||||
|
assert len(cfg.point_lights) == 3 # Default lights
|
||||||
|
|
||||||
|
def test_from_dict_none(self):
|
||||||
|
"""LightingConfig.from_dict handles None."""
|
||||||
|
cfg = LightingConfig.from_dict(None)
|
||||||
|
assert cfg.ambient_color == "#FFAA55"
|
||||||
|
assert len(cfg.point_lights) == 3
|
||||||
|
|
||||||
|
|
||||||
|
class TestEnvironmentConfig:
|
||||||
|
"""Tests for EnvironmentConfig dataclass."""
|
||||||
|
|
||||||
|
def test_default_values(self):
|
||||||
|
"""EnvironmentConfig has correct defaults."""
|
||||||
|
cfg = EnvironmentConfig()
|
||||||
|
assert cfg.rain_enabled is False
|
||||||
|
assert cfg.starfield_enabled is True # Matrix starfield
|
||||||
|
assert cfg.fog_color == "#0f0f23"
|
||||||
|
assert cfg.fog_density == 0.02
|
||||||
|
|
||||||
|
def test_from_dict_full(self):
|
||||||
|
"""EnvironmentConfig.from_dict loads all fields."""
|
||||||
|
data = {
|
||||||
|
"rain_enabled": True,
|
||||||
|
"starfield_enabled": False,
|
||||||
|
"fog_color": "#FFFFFF",
|
||||||
|
"fog_density": 0.1,
|
||||||
|
}
|
||||||
|
cfg = EnvironmentConfig.from_dict(data)
|
||||||
|
assert cfg.rain_enabled is True
|
||||||
|
assert cfg.starfield_enabled is False
|
||||||
|
assert cfg.fog_color == "#FFFFFF"
|
||||||
|
assert cfg.fog_density == 0.1
|
||||||
|
|
||||||
|
def test_from_dict_partial(self):
|
||||||
|
"""EnvironmentConfig.from_dict fills missing fields."""
|
||||||
|
data = {"rain_enabled": True}
|
||||||
|
cfg = EnvironmentConfig.from_dict(data)
|
||||||
|
assert cfg.rain_enabled is True
|
||||||
|
assert cfg.starfield_enabled is True # Default
|
||||||
|
assert cfg.fog_color == "#0f0f23"
|
||||||
|
|
||||||
|
|
||||||
|
class TestFeaturesConfig:
|
||||||
|
"""Tests for FeaturesConfig dataclass."""
|
||||||
|
|
||||||
|
def test_default_values_all_enabled(self):
|
||||||
|
"""FeaturesConfig defaults to all features enabled."""
|
||||||
|
cfg = FeaturesConfig()
|
||||||
|
assert cfg.chat_enabled is True
|
||||||
|
assert cfg.visitor_avatars is True
|
||||||
|
assert cfg.pip_familiar is True
|
||||||
|
assert cfg.workshop_portal is True
|
||||||
|
|
||||||
|
def test_from_dict_full(self):
|
||||||
|
"""FeaturesConfig.from_dict loads all fields."""
|
||||||
|
data = {
|
||||||
|
"chat_enabled": False,
|
||||||
|
"visitor_avatars": False,
|
||||||
|
"pip_familiar": False,
|
||||||
|
"workshop_portal": False,
|
||||||
|
}
|
||||||
|
cfg = FeaturesConfig.from_dict(data)
|
||||||
|
assert cfg.chat_enabled is False
|
||||||
|
assert cfg.visitor_avatars is False
|
||||||
|
assert cfg.pip_familiar is False
|
||||||
|
assert cfg.workshop_portal is False
|
||||||
|
|
||||||
|
def test_from_dict_partial(self):
|
||||||
|
"""FeaturesConfig.from_dict fills missing fields."""
|
||||||
|
data = {"chat_enabled": False}
|
||||||
|
cfg = FeaturesConfig.from_dict(data)
|
||||||
|
assert cfg.chat_enabled is False
|
||||||
|
assert cfg.visitor_avatars is True # Default
|
||||||
|
assert cfg.pip_familiar is True
|
||||||
|
assert cfg.workshop_portal is True
|
||||||
|
|
||||||
|
|
||||||
|
class TestAgentConfig:
|
||||||
|
"""Tests for AgentConfig dataclass."""
|
||||||
|
|
||||||
|
def test_default_values(self):
|
||||||
|
"""AgentConfig has correct defaults."""
|
||||||
|
cfg = AgentConfig()
|
||||||
|
assert cfg.name == ""
|
||||||
|
assert cfg.role == ""
|
||||||
|
assert cfg.enabled is True
|
||||||
|
|
||||||
|
def test_from_dict_full(self):
|
||||||
|
"""AgentConfig.from_dict loads all fields."""
|
||||||
|
data = {"name": "Timmy", "role": "guide", "enabled": False}
|
||||||
|
cfg = AgentConfig.from_dict(data)
|
||||||
|
assert cfg.name == "Timmy"
|
||||||
|
assert cfg.role == "guide"
|
||||||
|
assert cfg.enabled is False
|
||||||
|
|
||||||
|
|
||||||
|
class TestAgentsConfig:
|
||||||
|
"""Tests for AgentsConfig dataclass."""
|
||||||
|
|
||||||
|
def test_default_values(self):
|
||||||
|
"""AgentsConfig has correct defaults."""
|
||||||
|
cfg = AgentsConfig()
|
||||||
|
assert cfg.default_count == 5
|
||||||
|
assert cfg.max_count == 20
|
||||||
|
assert cfg.agents == []
|
||||||
|
|
||||||
|
def test_from_dict_with_agents(self):
|
||||||
|
"""AgentsConfig.from_dict loads agent list."""
|
||||||
|
data = {
|
||||||
|
"default_count": 10,
|
||||||
|
"max_count": 50,
|
||||||
|
"agents": [
|
||||||
|
{"name": "Timmy", "role": "guide", "enabled": True},
|
||||||
|
{"name": "Helper", "role": "assistant"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
cfg = AgentsConfig.from_dict(data)
|
||||||
|
assert cfg.default_count == 10
|
||||||
|
assert cfg.max_count == 50
|
||||||
|
assert len(cfg.agents) == 2
|
||||||
|
assert cfg.agents[0].name == "Timmy"
|
||||||
|
assert cfg.agents[1].enabled is True # Default
|
||||||
|
|
||||||
|
|
||||||
|
class TestMatrixConfig:
|
||||||
|
"""Tests for MatrixConfig dataclass."""
|
||||||
|
|
||||||
|
def test_default_values(self):
|
||||||
|
"""MatrixConfig has correct composite defaults."""
|
||||||
|
cfg = MatrixConfig()
|
||||||
|
assert isinstance(cfg.lighting, LightingConfig)
|
||||||
|
assert isinstance(cfg.environment, EnvironmentConfig)
|
||||||
|
assert isinstance(cfg.features, FeaturesConfig)
|
||||||
|
assert isinstance(cfg.agents, AgentsConfig)
|
||||||
|
# Check the blend
|
||||||
|
assert cfg.lighting.ambient_color == "#FFAA55"
|
||||||
|
assert cfg.environment.starfield_enabled is True
|
||||||
|
assert cfg.features.chat_enabled is True
|
||||||
|
|
||||||
|
def test_from_dict_full(self):
|
||||||
|
"""MatrixConfig.from_dict loads all sections."""
|
||||||
|
data = {
|
||||||
|
"lighting": {"ambient_color": "#000000"},
|
||||||
|
"environment": {"rain_enabled": True},
|
||||||
|
"features": {"chat_enabled": False},
|
||||||
|
"agents": {"default_count": 3},
|
||||||
|
}
|
||||||
|
cfg = MatrixConfig.from_dict(data)
|
||||||
|
assert cfg.lighting.ambient_color == "#000000"
|
||||||
|
assert cfg.environment.rain_enabled is True
|
||||||
|
assert cfg.features.chat_enabled is False
|
||||||
|
assert cfg.agents.default_count == 3
|
||||||
|
|
||||||
|
def test_from_dict_partial(self):
|
||||||
|
"""MatrixConfig.from_dict fills missing sections with defaults."""
|
||||||
|
data = {"lighting": {"ambient_color": "#111111"}}
|
||||||
|
cfg = MatrixConfig.from_dict(data)
|
||||||
|
assert cfg.lighting.ambient_color == "#111111"
|
||||||
|
assert cfg.environment.starfield_enabled is True # Default
|
||||||
|
assert cfg.features.pip_familiar is True # Default
|
||||||
|
|
||||||
|
def test_from_dict_none(self):
|
||||||
|
"""MatrixConfig.from_dict handles None."""
|
||||||
|
cfg = MatrixConfig.from_dict(None)
|
||||||
|
assert cfg.lighting.ambient_color == "#FFAA55"
|
||||||
|
assert cfg.features.chat_enabled is True
|
||||||
|
|
||||||
|
def test_to_dict_roundtrip(self):
|
||||||
|
"""MatrixConfig.to_dict produces serializable output."""
|
||||||
|
cfg = MatrixConfig()
|
||||||
|
data = cfg.to_dict()
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
assert "lighting" in data
|
||||||
|
assert "environment" in data
|
||||||
|
assert "features" in data
|
||||||
|
assert "agents" in data
|
||||||
|
# Verify point lights are included
|
||||||
|
assert len(data["lighting"]["point_lights"]) == 3
|
||||||
|
|
||||||
|
|
||||||
|
class TestLoadFromYaml:
|
||||||
|
"""Tests for load_from_yaml function."""
|
||||||
|
|
||||||
|
def test_loads_valid_yaml(self, tmp_path: Path):
|
||||||
|
"""load_from_yaml reads a valid YAML file."""
|
||||||
|
config_path = tmp_path / "matrix.yaml"
|
||||||
|
data = {
|
||||||
|
"lighting": {"ambient_color": "#TEST11"},
|
||||||
|
"features": {"chat_enabled": False},
|
||||||
|
}
|
||||||
|
config_path.write_text(yaml.safe_dump(data))
|
||||||
|
|
||||||
|
cfg = load_from_yaml(config_path)
|
||||||
|
assert cfg.lighting.ambient_color == "#TEST11"
|
||||||
|
assert cfg.features.chat_enabled is False
|
||||||
|
|
||||||
|
def test_missing_file_returns_defaults(self, tmp_path: Path):
|
||||||
|
"""load_from_yaml returns defaults when file doesn't exist."""
|
||||||
|
config_path = tmp_path / "nonexistent.yaml"
|
||||||
|
cfg = load_from_yaml(config_path)
|
||||||
|
assert cfg.lighting.ambient_color == "#FFAA55"
|
||||||
|
assert cfg.features.chat_enabled is True
|
||||||
|
|
||||||
|
def test_empty_file_returns_defaults(self, tmp_path: Path):
|
||||||
|
"""load_from_yaml returns defaults for empty file."""
|
||||||
|
config_path = tmp_path / "empty.yaml"
|
||||||
|
config_path.write_text("")
|
||||||
|
cfg = load_from_yaml(config_path)
|
||||||
|
assert cfg.lighting.ambient_color == "#FFAA55"
|
||||||
|
|
||||||
|
def test_invalid_yaml_returns_defaults(self, tmp_path: Path):
|
||||||
|
"""load_from_yaml returns defaults for invalid YAML."""
|
||||||
|
config_path = tmp_path / "invalid.yaml"
|
||||||
|
config_path.write_text("not: valid: yaml: [")
|
||||||
|
cfg = load_from_yaml(config_path)
|
||||||
|
assert cfg.lighting.ambient_color == "#FFAA55"
|
||||||
|
assert cfg.features.chat_enabled is True
|
||||||
|
|
||||||
|
def test_non_dict_yaml_returns_defaults(self, tmp_path: Path):
|
||||||
|
"""load_from_yaml returns defaults when YAML is not a dict."""
|
||||||
|
config_path = tmp_path / "list.yaml"
|
||||||
|
config_path.write_text("- item1\n- item2")
|
||||||
|
cfg = load_from_yaml(config_path)
|
||||||
|
assert cfg.lighting.ambient_color == "#FFAA55"
|
||||||
|
|
||||||
|
def test_loads_actual_config_file(self):
|
||||||
|
"""load_from_yaml can load the project's config/matrix.yaml."""
|
||||||
|
repo_root = Path(__file__).parent.parent.parent
|
||||||
|
config_path = repo_root / "config" / "matrix.yaml"
|
||||||
|
if not config_path.exists():
|
||||||
|
pytest.skip("config/matrix.yaml not found")
|
||||||
|
|
||||||
|
cfg = load_from_yaml(config_path)
|
||||||
|
# Verify it loaded with expected values
|
||||||
|
assert cfg.lighting.ambient_color == "#FFAA55"
|
||||||
|
assert len(cfg.lighting.point_lights) == 3
|
||||||
|
assert cfg.environment.starfield_enabled is True
|
||||||
|
assert cfg.features.workshop_portal is True
|
||||||
|
|
||||||
|
def test_str_path_accepted(self, tmp_path: Path):
|
||||||
|
"""load_from_yaml accepts string path."""
|
||||||
|
config_path = tmp_path / "matrix.yaml"
|
||||||
|
config_path.write_text(yaml.safe_dump({"lighting": {"ambient_intensity": 0.9}}))
|
||||||
|
|
||||||
|
cfg = load_from_yaml(str(config_path))
|
||||||
|
assert cfg.lighting.ambient_intensity == 0.9
|
||||||
Reference in New Issue
Block a user