diff --git a/tests/test_deploy_config_validator.py b/tests/test_deploy_config_validator.py new file mode 100644 index 00000000..84dc9a9b --- /dev/null +++ b/tests/test_deploy_config_validator.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""Tests for deploy_config_validator.py""" + +import json +import sys +import os +import pytest + +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) +from scripts.deploy_config_validator import ( + validate_yaml_syntax, + validate_required_keys, + validate_provider_chain, + validate_value_types, + validate_config, + detect_config_type, + ValidationError, +) + + +class TestYAMLSyntax: + def test_valid_yaml(self): + data, errors = validate_yaml_syntax("key: value\nlist:\n - a\n - b\n") + assert data is not None + assert len(errors) == 0 + + def test_invalid_yaml(self): + data, errors = validate_yaml_syntax("key: [unclosed") + assert data is None + assert len(errors) > 0 + + def test_empty_yaml(self): + data, errors = validate_yaml_syntax("") + assert data is None + assert any("empty" in e.message for e in errors) + + def test_tabs_warning(self): + data, errors = validate_yaml_syntax("key:\tvalue\n") + assert any("tab" in e.message for e in errors) + + +class TestRequiredKeys: + def test_missing_key(self): + errors = validate_required_keys({}, "hermes") + assert any("providers" in e.message for e in errors) + + def test_wrong_type(self): + errors = validate_required_keys({"providers": "not-a-list"}, "hermes") + assert any("expected list" in e.message for e in errors) + + def test_valid(self): + errors = validate_required_keys({"providers": []}, "hermes") + provider_errors = [e for e in errors if "providers" in e.message and "missing" in e.message] + assert len(provider_errors) == 0 + + +class TestProviderChain: + def test_empty_providers(self): + errors = validate_provider_chain({"providers": []}) + assert any("empty" in e.message for e in errors) + + def test_missing_name(self): + errors = validate_provider_chain({"providers": [{"model": "test", "base_url": "http://x"}]}) + assert any("name" in e.message and "missing" in e.message for e in errors) + + def test_banned_provider(self): + errors = validate_provider_chain({"providers": [ + {"name": "anthropic", "model": "claude-3", "base_url": "http://x"} + ]}) + assert any("banned provider" in e.message for e in errors) + + def test_banned_model(self): + errors = validate_provider_chain({"providers": [ + {"name": "test", "model": "claude-sonnet-4", "base_url": "http://x"} + ]}) + assert any("banned model" in e.message for e in errors) + + def test_valid_providers(self): + errors = validate_provider_chain({"providers": [ + {"name": "kimi-coding", "model": "kimi-k2.5", "base_url": "https://api.kimi.com/v1"} + ]}) + provider_errors = [e for e in errors if e.severity == "error"] + assert len(provider_errors) == 0 + + +class TestValueTypes: + def test_string_port(self): + errors = validate_value_types({"port": "8080"}) + assert any("port" in e.path and "number" in e.message for e in errors) + + def test_valid_port(self): + errors = validate_value_types({"port": 8080}) + port_errors = [e for e in errors if "port" in e.path] + assert len(port_errors) == 0 + + def test_bad_url(self): + errors = validate_value_types({"base_url": "not-a-url"}) + assert any("URL" in e.message for e in errors) + + +class TestDetectConfigType: + def test_hermes(self): + t = detect_config_type({"providers": [], "display": {}}) + assert t == "hermes" + + def test_ansible(self): + t = detect_config_type({"all": {"children": {"wizards": {}}}}) + assert t == "ansible_inventory" + + def test_unknown(self): + t = detect_config_type({"random": "data"}) + assert t == "any" + + +class TestFullValidation: + def test_valid_hermes_config(self): + text = """ +providers: + - name: kimi-coding + model: kimi-k2.5 + base_url: https://api.kimi.com/coding/v1 + timeout: 120 +display: + skin: default +""" + errors = validate_config(text, "hermes") + assert not any(e.severity == "error" for e in errors) + + def test_banned_provider_catches(self): + text = """ +providers: + - name: anthropic + model: claude-sonnet-4 + base_url: https://api.anthropic.com +""" + errors = validate_config(text, "hermes") + assert any("banned" in e.message for e in errors) + + def test_missing_providers(self): + text = "display:\n skin: default\n" + errors = validate_config(text, "hermes") + assert any("providers" in e.message and "missing" in e.message for e in errors)