Spaces:
Running
Running
| """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: | |
| 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 | |