Files
ZNJJ-api-server/test/storage/test_storage_config_and_locks.py
T

90 lines
3.5 KiB
Python

import asyncio
import os
from dataclasses import replace
from uuid import uuid4
import pytest
from src.core.config import Config
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
from src.storage.runtime import StorageSettings, open_storage
from .test_persistence import run
@pytest.fixture
def postgres_settings():
dsn = os.getenv('AGENT_TEST_POSTGRES_DSN')
if not dsn:
pytest.skip('Set AGENT_TEST_POSTGRES_DSN to run PostgreSQL integration tests')
return StorageSettings(kind='postgresql',postgres_dsn=dsn,auto_migrate=True,pool_size=2)
def test_sqlite_rejects_second_instance_and_releases_guard(tmp_path):
async def scenario():
config = StorageSettings(kind='sqlite',sqlite_path=str(tmp_path/'single.db'),auto_migrate=True)
async with open_storage(config):
with pytest.raises(RuntimeError, match='active instance'):
async with open_storage(config): pass
async with open_storage(config): pass
run(scenario())
def test_sqlite_requires_migration_and_does_not_fall_back(tmp_path):
async def scenario():
config = StorageSettings(kind='sqlite',sqlite_path=str(tmp_path/'empty.db'))
with pytest.raises(Exception):
async with open_storage(config):
pytest.fail('Uninitialized durable storage must not start')
async with open_storage(replace(config,auto_migrate=True)): pass
async with open_storage(config) as runtime:
assert isinstance(runtime.checkpointer, AsyncSqliteSaver)
run(scenario())
def test_postgresql_locks_serialize_independent_application_instances(postgres_settings):
settings = postgres_settings
async def scenario():
session = uuid4().hex
async with open_storage(settings) as first, open_storage(settings) as second:
entered = asyncio.Event()
async def waiting():
async with second.locks.hold(session): entered.set()
async with first.locks.hold(session):
task=asyncio.create_task(waiting())
await asyncio.sleep(0.15)
assert not entered.is_set()
# 同一个锁池中,不同会话仍能执行。
async with second.locks.hold(session+'-other'):
pass
await asyncio.wait_for(task,3)
assert entered.is_set()
run(scenario())
def test_postgresql_cancelled_lock_holder_releases_server_lock(postgres_settings):
settings = postgres_settings
async def scenario():
session=uuid4().hex
async with open_storage(settings) as first, open_storage(settings) as second:
ready=asyncio.Event()
async def holding():
async with first.locks.hold(session):
ready.set()
await asyncio.Future()
task=asyncio.create_task(holding())
await ready.wait()
task.cancel()
with pytest.raises(asyncio.CancelledError): await task
async with asyncio.timeout(3):
async with second.locks.hold(session): pass
run(scenario())
@pytest.mark.parametrize('kind', ['unknown', 'sqlite', 'postgresql'])
def test_invalid_storage_config_is_rejected(monkeypatch,kind):
monkeypatch.setattr(Config,'AGENT_BACKEND','langgraph')
monkeypatch.setattr(Config,'AGENT_STORAGE',kind)
monkeypatch.setattr(Config,'AGENT_SQLITE_PATH',':memory:')
monkeypatch.setattr(Config,'AGENT_POSTGRES_DSN','')
with pytest.raises(ValueError):Config.validate()