186 lines
6.2 KiB
Python
186 lines
6.2 KiB
Python
"""
|
|
Offline tests for OpenSearch support in LLM cache tools.
|
|
"""
|
|
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
pytest.importorskip(
|
|
"opensearchpy",
|
|
reason="opensearchpy is required for OpenSearch tool tests",
|
|
)
|
|
|
|
from lightrag.tools.clean_llm_query_cache import CleanupStats, CleanupTool
|
|
from lightrag.tools.migrate_llm_cache import MigrationTool
|
|
|
|
pytestmark = pytest.mark.offline
|
|
|
|
|
|
class FakeOpenSearchStorage:
|
|
def __init__(self, batches, workspace="test-workspace"):
|
|
self._batches = batches
|
|
self.workspace = workspace
|
|
self.deleted_batches = []
|
|
|
|
async def _iter_raw_docs(self, batch_size=1000):
|
|
for batch in self._batches:
|
|
yield batch
|
|
|
|
async def delete(self, ids):
|
|
self.deleted_batches.append(list(ids))
|
|
|
|
|
|
def _flatten(batches):
|
|
return [item for batch in batches for item in batch]
|
|
|
|
|
|
class TestCleanupToolOpenSearch:
|
|
@pytest.mark.asyncio
|
|
async def test_count_query_caches_opensearch(self):
|
|
tool = CleanupTool()
|
|
storage = FakeOpenSearchStorage(
|
|
[
|
|
[
|
|
{"_id": "mix:query:1", "_source": {}},
|
|
{"_id": "mix:keywords:1", "_source": {}},
|
|
{"_id": "default:extract:1", "_source": {}},
|
|
],
|
|
[
|
|
{"_id": "hybrid:query:1", "_source": {}},
|
|
{"_id": "local:keywords:1", "_source": {}},
|
|
{"_id": "other:key:1", "_source": {}},
|
|
],
|
|
]
|
|
)
|
|
|
|
counts = await tool.count_query_caches(storage, "OpenSearchKVStorage")
|
|
|
|
assert counts["mix"] == {"query": 1, "keywords": 1}
|
|
assert counts["hybrid"] == {"query": 1, "keywords": 0}
|
|
assert counts["local"] == {"query": 0, "keywords": 1}
|
|
assert counts["global"] == {"query": 0, "keywords": 0}
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
("cleanup_type", "expected_ids"),
|
|
[
|
|
(
|
|
"all",
|
|
[
|
|
"mix:query:1",
|
|
"mix:keywords:1",
|
|
"global:query:1",
|
|
"local:keywords:1",
|
|
],
|
|
),
|
|
("query", ["mix:query:1", "global:query:1"]),
|
|
("keywords", ["mix:keywords:1", "local:keywords:1"]),
|
|
],
|
|
)
|
|
async def test_delete_query_caches_opensearch(self, cleanup_type, expected_ids):
|
|
tool = CleanupTool()
|
|
tool.batch_size = 2
|
|
storage = FakeOpenSearchStorage(
|
|
[
|
|
[
|
|
{"_id": "mix:query:1", "_source": {}},
|
|
{"_id": "mix:keywords:1", "_source": {}},
|
|
],
|
|
[
|
|
{"_id": "global:query:1", "_source": {}},
|
|
{"_id": "local:keywords:1", "_source": {}},
|
|
{"_id": "default:extract:1", "_source": {}},
|
|
],
|
|
]
|
|
)
|
|
stats = CleanupStats()
|
|
|
|
await tool.delete_query_caches(
|
|
storage, "OpenSearchKVStorage", cleanup_type, stats
|
|
)
|
|
|
|
assert _flatten(storage.deleted_batches) == expected_ids
|
|
assert all(len(batch) <= 2 for batch in storage.deleted_batches)
|
|
assert stats.successfully_deleted == len(expected_ids)
|
|
assert stats.successful_batches == len(storage.deleted_batches)
|
|
|
|
def test_check_config_ini_for_storage_opensearch(self, tmp_path, monkeypatch):
|
|
monkeypatch.chdir(tmp_path)
|
|
(tmp_path / "config.ini").write_text("[opensearch]\nhosts = localhost:9200\n")
|
|
|
|
assert CleanupTool().check_config_ini_for_storage("OpenSearchKVStorage")
|
|
|
|
def test_get_storage_class_opensearch(self):
|
|
cleanup_cls = CleanupTool().get_storage_class("OpenSearchKVStorage")
|
|
migrate_cls = MigrationTool().get_storage_class("OpenSearchKVStorage")
|
|
|
|
assert cleanup_cls.__name__ == "OpenSearchKVStorage"
|
|
assert migrate_cls.__name__ == "OpenSearchKVStorage"
|
|
|
|
|
|
class TestMigrationToolOpenSearch:
|
|
@pytest.mark.asyncio
|
|
async def test_count_and_stream_default_caches_opensearch(self):
|
|
tool = MigrationTool()
|
|
storage = FakeOpenSearchStorage(
|
|
[
|
|
[
|
|
{"_id": "default:extract:1", "_source": {"return": "a"}},
|
|
{"_id": "mix:query:1", "_source": {"return": "ignored"}},
|
|
],
|
|
[
|
|
{"_id": "default:summary:1", "_source": {"return": "b"}},
|
|
{"_id": "default:extract:2", "_source": {"return": "c"}},
|
|
],
|
|
]
|
|
)
|
|
|
|
count = await tool.count_default_caches(storage, "OpenSearchKVStorage")
|
|
streamed = [
|
|
batch
|
|
async for batch in tool.stream_default_caches(
|
|
storage, "OpenSearchKVStorage", batch_size=2
|
|
)
|
|
]
|
|
|
|
assert count == 3
|
|
assert streamed == [
|
|
{
|
|
"default:extract:1": {"return": "a"},
|
|
"default:summary:1": {"return": "b"},
|
|
},
|
|
{"default:extract:2": {"return": "c"}},
|
|
]
|
|
|
|
def test_count_available_storage_types_includes_opensearch(
|
|
self, tmp_path, monkeypatch
|
|
):
|
|
monkeypatch.chdir(tmp_path)
|
|
(tmp_path / "config.ini").write_text("[opensearch]\nhosts = localhost:9200\n")
|
|
|
|
with patch.dict("os.environ", {}, clear=True):
|
|
assert MigrationTool().count_available_storage_types() == 2
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_setup_storage_returns_effective_workspace(self, monkeypatch):
|
|
tool = MigrationTool()
|
|
fake_storage = SimpleNamespace(workspace="forced-workspace")
|
|
|
|
monkeypatch.setattr(tool, "check_env_vars", lambda _: True)
|
|
monkeypatch.setattr(
|
|
tool, "initialize_storage", AsyncMock(return_value=fake_storage)
|
|
)
|
|
monkeypatch.setattr(tool, "count_default_caches", AsyncMock(return_value=3))
|
|
|
|
with patch("builtins.input", return_value="5"):
|
|
storage, storage_name, workspace, total_count = await tool.setup_storage(
|
|
"Source", use_streaming=True
|
|
)
|
|
|
|
assert storage is fake_storage
|
|
assert storage_name == "OpenSearchKVStorage"
|
|
assert workspace == "forced-workspace"
|
|
assert total_count == 3
|