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()