""" Tests for state_machine.py — apply_effect для всех типов операций. """ import os import sys import pytest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "site")) import mock_state import state_machine # Минимальный services-словарь для тестов SERVICES = { 1: { "name": "dummy", "service_id": 1, "service_display_name": "Болванка", "stateParams": {"resourceRealm": "dummy", "durationMs": "0"}, "stateOut": {}, "cfsParams": [ {"svcOperationCfsParamId": 242, "svcOperationCfsParam": "resourceRealm", "dataType": "string"}, {"svcOperationCfsParamId": 198, "svcOperationCfsParam": "durationMs", "dataType": "integer"}, ], "cfsParamsByOp": {18: [242, 198], 92: [242, 198]}, "operations": [ {"svcOperationId": 18, "operation": "create", "kind": "instance", "action": "create"}, {"svcOperationId": 92, "operation": "modify", "kind": "instance", "action": "modify"}, {"svcOperationId": 71, "operation": "delete", "kind": "instance", "action": "delete"}, {"svcOperationId": 93, "operation": "suspend", "kind": "instance", "action": "suspend"}, {"svcOperationId": 94, "operation": "resume", "kind": "instance", "action": "resume"}, ], }, 90: { "name": "postgres", "service_id": 90, "service_display_name": "PostgreSQL", "stateParams": {"version": "17"}, "stateOut": {"users": {}, "databases": {}}, "cfsParams": [], "cfsParamsByOp": {}, "operations": [ {"svcOperationId": 19, "operation": "create", "kind": "instance", "action": "create"}, {"svcOperationId": 44, "operation": "create_user", "kind": "subresource", "action": "create"}, {"svcOperationId": 45, "operation": "delete_user", "kind": "subresource", "action": "delete"}, ], }, } @pytest.fixture(autouse=True) def reset_state(): """Перед каждым тестом — чистый state.""" mock_state.state.reset() yield def _create_and_run(service_id, display_name, svc_op_id, operation, kind="instance", action="create"): """Хелпер: создать инстанс + операцию + run. Возвращает (instanceUid, opUid).""" st = mock_state.state uid = st.create_instance(service_id, display_name, SERVICES[service_id]) op_uid = st.create_operation(uid, svc_op_id, operation, kind, action) state_machine.apply_effect(op_uid, st, SERVICES) return uid, op_uid def _run_op(instance_uid, svc_op_id, operation, kind="instance", action="create"): """Хелпер: создать операцию на СУЩЕСТВУЮЩЕМ инстансе + run. Возвращает opUid.""" st = mock_state.state op_uid = st.create_operation(instance_uid, svc_op_id, operation, kind, action) state_machine.apply_effect(op_uid, st, SERVICES) return op_uid class TestInstanceOperations: """Тесты instance-операций.""" def test_create_sets_running_and_params(self): """create → статус running, params из шаблона.""" uid, _ = _create_and_run(1, "test", 18, "create") inst = mock_state.state.get_instance(uid) assert inst["status"] == "running" assert inst["state"]["params"]["resourceRealm"] == "dummy" assert inst["state"]["params"]["durationMs"] == "0" def test_create_sets_state_out(self): """create → state.out из шаблона (пустой для dummy, заполнен для pg).""" uid, _ = _create_and_run(90, "pg", 19, "create") inst = mock_state.state.get_instance(uid) assert "users" in inst["state"]["out"] assert "databases" in inst["state"]["out"] def test_delete_removes_instance(self): """delete → инстанс удалён.""" uid, _ = _create_and_run(1, "test", 18, "create") _run_op(uid, 71, "delete", action="delete") assert mock_state.state.get_instance(uid) is None def test_suspend_sets_suspended(self): """suspend → статус suspended.""" uid, _ = _create_and_run(1, "test", 18, "create") _run_op(uid, 93, "suspend", action="suspend") inst = mock_state.state.get_instance(uid) assert inst["status"] == "suspended" def test_resume_sets_running(self): """resume → статус running.""" uid, _ = _create_and_run(1, "test", 18, "create") _run_op(uid, 93, "suspend", action="suspend") _run_op(uid, 94, "resume", action="resume") inst = mock_state.state.get_instance(uid) assert inst["status"] == "running" def test_modify_merges_params(self): """modify → params мержатся.""" uid, _ = _create_and_run(1, "test", 18, "create") # Установить новый paramValue через op_params op2 = _run_op(uid, 92, "modify", action="modify") mock_state.state.op_params[op2][198] = "999" state_machine.apply_effect(op2, mock_state.state, SERVICES) inst = mock_state.state.get_instance(uid) assert inst["state"]["params"]["durationMs"] == "999" # resourceRealm остался неизменным assert inst["state"]["params"]["resourceRealm"] == "dummy" class TestSubresourceOperations: """Тесты subresource-операций.""" def test_create_user_adds_to_state_out(self): """create_user → state.out.users[name] = {}.""" uid, _ = _create_and_run(90, "pg", 19, "create") op2 = _run_op(uid, 44, "create_user", kind="subresource", action="create") mock_state.state.op_params[op2][999] = "alice" state_machine.apply_effect(op2, mock_state.state, SERVICES) inst = mock_state.state.get_instance(uid) assert "alice" in inst["state"]["out"].get("users", {}) def test_delete_user_removes_from_state_out(self): """delete_user → state.out.users[name] удалён.""" uid, _ = _create_and_run(90, "pg", 19, "create") # Добавить пользователя вручную inst = mock_state.state.get_instance(uid) inst["state"]["out"]["users"]["alice"] = {} # Удалить op2 = _run_op(uid, 45, "delete_user", kind="subresource", action="delete") mock_state.state.op_params[op2][999] = "alice" state_machine.apply_effect(op2, mock_state.state, SERVICES) assert "alice" not in inst["state"]["out"]["users"] class TestReset: """Тест сброса состояния.""" def test_reset_clears_all(self): """reset → всё пусто.""" _create_and_run(1, "t1", 18, "create") _create_and_run(1, "t2", 18, "create") mock_state.state.reset() assert len(mock_state.state.instances) == 0 assert len(mock_state.state.operations) == 0