import unittest from utils.config_validate import ConfigError, require, validate_temp_ctrl def _valid_temp_ctrl_config(): gains = {"kp": 0.08, "ki": 0.02, "kd": 0.0, "kt": 1.5} return { "TempCtrl": { "pid_type": "Smith", "Outer": dict(gains), "Inner": { "Heat": dict(gains), "Hold": dict(gains), "Cool": dict(gains), }, } } class TestRequire(unittest.TestCase): def test_returns_value_when_present(self): self.assertEqual(require({"a": {"b": 1}}, "a.b"), 1) def test_raises_on_missing_leaf(self): with self.assertRaises(ConfigError): require({"a": {}}, "a.b") def test_raises_on_missing_intermediate(self): with self.assertRaises(ConfigError): require({}, "a.b") def test_error_names_full_path_up_to_missing_key(self): with self.assertRaisesRegex(ConfigError, r"a\.b"): require({"a": {}}, "a.b.c") def test_raises_on_wrong_type(self): with self.assertRaises(ConfigError): require({"a": "not a number"}, "a", type=(int, float)) def test_accepts_matching_type(self): self.assertEqual(require({"a": 1.5}, "a", type=(int, float)), 1.5) class TestValidateTempCtrl(unittest.TestCase): def test_valid_config_passes(self): validate_temp_ctrl(_valid_temp_ctrl_config()) def test_missing_gain_raises(self): config = _valid_temp_ctrl_config() del config["TempCtrl"]["Inner"]["Hold"]["kp"] with self.assertRaisesRegex(ConfigError, r"TempCtrl\.Inner\.Hold\.kp"): validate_temp_ctrl(config) def test_missing_pid_type_raises(self): config = _valid_temp_ctrl_config() del config["TempCtrl"]["pid_type"] with self.assertRaises(ConfigError): validate_temp_ctrl(config) def test_optional_keys_not_required(self): config = _valid_temp_ctrl_config() # Outer.y_hold_min, Inner.Hold.yi_max, and the whole Thresholds # section all have their own defaults elsewhere and must stay # optional here too. validate_temp_ctrl(config) self.assertNotIn("y_hold_min", config["TempCtrl"]["Outer"]) self.assertNotIn("Thresholds", config["TempCtrl"]) if __name__ == '__main__': unittest.main()