gMAS / tests /test_state_storage.py
Артём Боярских
chore: initial commit
3193174
"""Tests for src/utils/state_storage.py"""
import pytest
from utils.state_storage import FileStateStorage, InMemoryStateStorage
# ─────────────────────────── InMemoryStateStorage ─────────────────────────────
class TestInMemoryStateStorage:
def setup_method(self):
self.storage = InMemoryStateStorage()
def test_save_and_load(self):
self.storage.save("node1", {"key": "value"})
result = self.storage.load("node1")
assert result == {"key": "value"}
def test_load_missing_returns_none(self):
result = self.storage.load("nonexistent")
assert result is None
def test_delete_existing(self):
self.storage.save("node1", {"key": "val"})
self.storage.delete("node1")
assert self.storage.load("node1") is None
def test_delete_missing_no_error(self):
self.storage.delete("nonexistent") # should not raise
def test_keys_empty(self):
assert self.storage.keys() == []
def test_keys_after_saves(self):
self.storage.save("a", {})
self.storage.save("b", {})
keys = self.storage.keys()
assert "a" in keys
assert "b" in keys
assert len(keys) == 2
def test_keys_after_delete(self):
self.storage.save("a", {})
self.storage.save("b", {})
self.storage.delete("a")
keys = self.storage.keys()
assert "a" not in keys
assert "b" in keys
def test_clear(self):
self.storage.save("a", {"x": 1})
self.storage.save("b", {"y": 2})
self.storage.clear()
assert self.storage.keys() == []
assert self.storage.load("a") is None
def test_overwrite(self):
self.storage.save("node1", {"v": 1})
self.storage.save("node1", {"v": 2})
assert self.storage.load("node1") == {"v": 2}
def test_save_complex_state(self):
state = {
"messages": [{"role": "user", "content": "hi"}],
"tokens": 42,
"nested": {"a": {"b": "c"}},
}
self.storage.save("complex", state)
result = self.storage.load("complex")
assert result == state
def test_multiple_nodes_independent(self):
self.storage.save("n1", {"data": "first"})
self.storage.save("n2", {"data": "second"})
assert self.storage.load("n1") == {"data": "first"}
assert self.storage.load("n2") == {"data": "second"}
# ─────────────────────────── FileStateStorage ─────────────────────────────────
class TestFileStateStorage:
@pytest.fixture
def storage(self, tmp_path):
return FileStateStorage(tmp_path / "states")
def test_save_and_load(self, storage):
storage.save("node1", {"key": "value"})
result = storage.load("node1")
assert result == {"key": "value"}
def test_load_missing_returns_none(self, storage):
result = storage.load("nonexistent")
assert result is None
def test_delete_existing(self, storage):
storage.save("node1", {"key": "val"})
storage.delete("node1")
assert storage.load("node1") is None
def test_delete_missing_no_error(self, storage):
storage.delete("nonexistent") # should not raise
def test_keys_empty(self, storage):
assert storage.keys() == []
def test_keys_after_saves(self, storage):
storage.save("a", {})
storage.save("b", {})
keys = storage.keys()
assert "a" in keys
assert "b" in keys
def test_keys_after_delete(self, storage):
storage.save("a", {})
storage.save("b", {})
storage.delete("a")
keys = storage.keys()
assert "a" not in keys
def test_clear(self, storage):
storage.save("a", {"x": 1})
storage.save("b", {"y": 2})
storage.clear()
assert storage.keys() == []
assert storage.load("a") is None
def test_overwrite(self, storage):
storage.save("node1", {"v": 1})
storage.save("node1", {"v": 2})
assert storage.load("node1") == {"v": 2}
def test_safe_node_id_chars(self, storage):
"""Node IDs with special chars should be sanitized to safe filenames."""
storage.save("agent/with:special", {"data": 42})
result = storage.load("agent/with:special")
assert result == {"data": 42}
def test_complex_state(self, storage):
state = {
"messages": [{"role": "user", "content": "hello"}],
"nested": {"key": [1, 2, 3]},
}
storage.save("node1", state)
result = storage.load("node1")
assert result == state
def test_creates_directory(self, tmp_path):
deep_path = tmp_path / "a" / "b" / "c"
storage = FileStateStorage(deep_path)
storage.save("test", {"v": 1})
assert storage.load("test") == {"v": 1}
def test_unicode_state(self, storage):
state = {"message": "ΠΏΡ€ΠΈΠ²Π΅Ρ‚ ΠΌΠΈΡ€", "emoji": "πŸš€"}
storage.save("unicode_node", state)
result = storage.load("unicode_node")
assert result == state
def test_large_state(self, storage):
state = {"data": list(range(1000))}
storage.save("large_node", state)
result = storage.load("large_node")
assert result == state
def test_keys_with_invalid_json_file(self, tmp_path):
"""keys() skips files with invalid JSON (lines 67-68)."""
import json
storage = FileStateStorage(str(tmp_path))
# Write a valid file
valid_file = tmp_path / "valid.json"
valid_file.write_text(json.dumps({"node_id": "valid_node"}))
# Write a file with invalid JSON
invalid_file = tmp_path / "invalid.json"
invalid_file.write_text("not valid json {")
# Write a file missing node_id
missing_id_file = tmp_path / "missing.json"
missing_id_file.write_text(json.dumps({"other_key": "value"}))
keys = storage.keys()
assert "valid_node" in keys
assert len([k for k in keys if k == "valid_node"]) == 1