"""Testy SettingsManager — validace, persistence, klasifikace polí.""" def test_defaults_from_env(settings_module, monkeypatch): monkeypatch.setenv("MODEL_PRIMARY", "org/env-model") monkeypatch.setenv("MAX_STEPS", "42") s = settings_module.Settings() assert s.model == "org/env-model" assert s.max_steps == 42 assert s.tensor_parallel_size == 4 assert s.gpu_memory_utilization == 0.92 def test_update_instant_field(settings_module): mgr = settings_module.SettingsManager() applied, reload_needed, errors = mgr.update({"max_steps": "40"}) assert applied == {"max_steps": 40} assert reload_needed is False assert errors == {} assert mgr.get().max_steps == 40 assert mgr.revision == 1 def test_update_engine_field_flags_reload(settings_module): mgr = settings_module.SettingsManager() applied, reload_needed, errors = mgr.update({"model": "org/new-model"}) assert applied == {"model": "org/new-model"} assert reload_needed is True assert errors == {} def test_no_change_no_revision_bump(settings_module): mgr = settings_module.SettingsManager() current = mgr.get().max_steps applied, reload_needed, _ = mgr.update({"max_steps": current}) assert applied == {} assert reload_needed is False assert mgr.revision == 0 def test_validation_errors(settings_module): mgr = settings_module.SettingsManager() applied, _, errors = mgr.update({ "gpu_memory_utilization": "1.5", "tensor_parallel_size": "16", "agent_mode": "dual", "runner_url": "ftp://spatne", "neexistujici_pole": "x", "max_output_tokens": "abc", }) assert applied == {} assert set(errors) == {"gpu_memory_utilization", "tensor_parallel_size", "agent_mode", "runner_url", "neexistujici_pole", "max_output_tokens"} def test_bool_and_float_coercion(settings_module): mgr = settings_module.SettingsManager() applied, reload_needed, errors = mgr.update({ "enforce_eager": "true", "temperature": "0.7", "enable_prefix_caching": False, }) assert errors == {} assert applied["enforce_eager"] is True assert applied["temperature"] == 0.7 assert applied["enable_prefix_caching"] is False assert reload_needed is True # oba bool patří mezi engine pole def test_persistence_roundtrip(settings_module, env): mgr = settings_module.SettingsManager() mgr.update({"model": "org/persisted", "max_steps": 55}) # nový manager se stejnou cestou načte uložené hodnoty mgr2 = settings_module.SettingsManager(path=mgr.path) assert mgr2.get().model == "org/persisted" assert mgr2.get().max_steps == 55 def test_masking_secrets(settings_module): mgr = settings_module.SettingsManager() mgr.update({"runner_token": "super-secret"}) data = mgr.as_dict() assert data["runner_token"] == "********" # maskovaná hodnota poslaná zpět z UI nesmí přepsat skutečný token applied, _, _ = mgr.update({"runner_token": "********"}) assert applied == {} assert mgr.get().runner_token == "super-secret" def test_download_dir_validation(settings_module): mgr = settings_module.SettingsManager() # relativní cesta => chyba applied, _, errors = mgr.update({"download_dir": "relativni/cesta"}) assert applied == {} and "download_dir" in errors # absolutní cesta => OK, jde o enginové pole (reload) applied, reload_needed, errors = mgr.update({"download_dir": "/data/models/"}) assert errors == {} assert applied == {"download_dir": "/data/models"} # trailing / oříznut assert reload_needed is True # prázdné = auto => OK applied, _, errors = mgr.update({"download_dir": ""}) assert errors == {} and applied == {"download_dir": ""} def test_corrupted_persist_file_ignored(settings_module, env): path = env["tmp"] / "settings.json" path.write_text("{ rozbité json ", encoding="utf-8") mgr = settings_module.SettingsManager(path=path) assert mgr.get().tensor_parallel_size == 4 # defaulty přežily