""" Tests for from_stands.py — конвертер STANDS YAML → polygon config. """ import os import tempfile import pytest import yaml # Добавляем site/ в path для импорта from_stands import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "site")) import from_stands @pytest.fixture def dummy_yaml(): """Минимальный STANDS YAML для тестов.""" return { "name": "dummy", "service_id": 1, "service_display_name": "Болванка", "service_short_name": "dummy", "lifecycle": { "suspend_on_destroy_default": True, "adopt_existing_on_create_default": False, }, "operations": [ { "name": "create", "id": 18, "kind": "instance", "action": "create", "params": [ { "id": 242, "code": "resourceRealm", "data_type": "string", "required": True, "default": "dummy", "value_list": ["dummy"], }, { "id": 198, "code": "durationMs", "data_type": "integer >= 0", "required": True, "default": "0", }, ], }, {"name": "delete", "id": 71, "kind": "instance", "action": "delete"}, ], } @pytest.fixture def pg_yaml(): """STANDS YAML с subresources для тестов.""" return { "name": "postgres", "service_id": 90, "service_display_name": "PostgreSQL", "service_short_name": "postgres", "lifecycle": { "suspend_on_destroy_default": True, "adopt_existing_on_create_default": False, }, "operations": [ { "name": "create", "id": 19, "kind": "instance", "action": "create", "params": [ { "id": 788, "code": "clusterConfiguration", "data_type": "map-fixed", "required": True, "sub_params": [ {"id": 106, "code": "replicas", "data_type": "integer > 0", "required": True, "default": "1"}, {"id": 107, "code": "disk", "data_type": "integer > 0", "required": True, "default": "10"}, ], }, ], }, {"name": "create_user", "id": 44, "kind": "subresource", "action": "create"}, {"name": "delete_user", "id": 45, "kind": "subresource", "action": "delete"}, {"name": "create_database", "id": 46, "kind": "subresource", "action": "create"}, {"name": "delete_database", "id": 47, "kind": "subresource", "action": "delete"}, ], } class TestConvertService: """Тесты конвертации одного сервиса.""" def test_basic_fields(self, dummy_yaml): """Поля верхнего уровня переносятся 1:1.""" cfg = from_stands.convert_service_from_dict(dummy_yaml) assert cfg["name"] == "dummy" assert cfg["service_id"] == 1 assert cfg["service_display_name"] == "Болванка" def test_lifecycle(self, dummy_yaml): """lifecycle конвертируется в camelCase.""" cfg = from_stands.convert_service_from_dict(dummy_yaml) assert cfg["lifecycle"]["suspendOnDestroyDefault"] is True assert cfg["lifecycle"]["adoptExistingOnCreateDefault"] is False def test_operations_count(self, dummy_yaml): """Количество операций совпадает.""" cfg = from_stands.convert_service_from_dict(dummy_yaml) assert len(cfg["operations"]) == 2 def test_html_unescape(self, dummy_yaml): """HTML entities раскодируются: > → >.""" cfg = from_stands.convert_service_from_dict(dummy_yaml) cfs = {p["svcOperationCfsParamId"]: p for p in cfg["cfsParams"]} assert cfs[198]["dataType"] == "integer >= 0" assert ">" not in cfs[198]["dataType"] def test_value_list(self, dummy_yaml): """valueList конвертируется.""" cfg = from_stands.convert_service_from_dict(dummy_yaml) cfs = {p["svcOperationCfsParamId"]: p for p in cfg["cfsParams"]} assert cfs[242]["valueList"] == ["dummy"] def test_state_params_from_create(self, dummy_yaml): """stateParams генерируются из create-операции.""" cfg = from_stands.convert_service_from_dict(dummy_yaml) assert cfg["stateParams"]["resourceRealm"] == "dummy" assert cfg["stateParams"]["durationMs"] == "0" def test_data_descriptor_from_sub_params(self, pg_yaml): """map-fixed/sub_params → dataDescriptor.""" cfg = from_stands.convert_service_from_dict(pg_yaml) cfs = {p["svcOperationCfsParamId"]: p for p in cfg["cfsParams"]} dd = cfs[788]["dataDescriptor"] assert "replicas" in dd assert "disk" in dd assert dd["replicas"]["dataType"] == "integer > 0" assert dd["replicas"]["defaultValue"] == "1" def test_state_out_subresources(self, pg_yaml): """stateOut генерируется из subresource-операций.""" cfg = from_stands.convert_service_from_dict(pg_yaml) assert "users" in cfg["stateOut"] assert "databases" in cfg["stateOut"] def test_cfs_params_by_op(self, dummy_yaml): """cfsParamsByOp связывает opId → список paramId.""" cfg = from_stands.convert_service_from_dict(dummy_yaml) assert 18 in cfg["cfsParamsByOp"] assert 242 in cfg["cfsParamsByOp"][18] assert 198 in cfg["cfsParamsByOp"][18] class TestFileRoundtrip: """Тесты записи/чтения YAML.""" def test_roundtrip(self, dummy_yaml): """Сгенерированный YAML можно записать и прочитать обратно.""" with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: fname = f.name try: # Записать with open(fname, "w") as f: cfg = from_stands.convert_service_from_dict(dummy_yaml) yaml.dump(cfg, f, allow_unicode=True, default_flow_style=False) # Прочитать with open(fname) as f: loaded = yaml.safe_load(f) assert loaded["name"] == "dummy" assert len(loaded["cfsParams"]) == 2 finally: os.unlink(fname)