Files
polygon/tests/test_state_machine.py

164 lines
6.9 KiB
Python

"""
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