mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-08 03:30:34 +00:00
* fix(runtime): fence revoked run event producers * fix(runtime): reduce off-mode producer guard locking * docs: keep inherited guidance within size budget * chore(runtime): remove development phase labels * fix(persistence): normalize libpq SSL options for asyncpg connections * docs(runtime): clarify producer fencing behavior in off mode * test(persistence): align migration TLS probes with channel-binding rejection * fix(runtime): authorize fenced Gateway history seeding * fix(scheduler): preserve takeover identity in recovery publication * fix(scheduler): report unconfirmed recovery publication * fix(runtime): preserve history messages before the first human turn * fix(runtime): clarify run creation failure diagnostic --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
105 lines
4.3 KiB
Python
105 lines
4.3 KiB
Python
"""Async ORM URLs preserve TLS requirements without changing libpq consumers."""
|
|
|
|
import os
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from sqlalchemy import text
|
|
from sqlalchemy.engine import make_url
|
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
from support.postgres import asyncpg_test_url
|
|
|
|
from deerflow.config.database_config import DatabaseConfig
|
|
from deerflow.persistence import engine as engine_mod
|
|
|
|
|
|
@pytest.mark.parametrize("scheme", ["postgres", "postgresql", "postgresql+asyncpg"])
|
|
@pytest.mark.parametrize("mode", [None, "disable", "allow", "prefer", "require", "verify-ca", "verify-full"])
|
|
def test_async_orm_url_preserves_tls_credentials_and_query_options(scheme, mode):
|
|
uri = f"{scheme}://us%40er%3Aname:p%40ss%2Fword%3A%25@localhost:5432/deerflow_test?target_session_attrs=read-write"
|
|
if mode is not None:
|
|
uri += f"&sslmode={mode}"
|
|
config = DatabaseConfig(backend="postgres", postgres_url=uri)
|
|
url = make_url(config.app_sqlalchemy_url)
|
|
_, kwargs = url.get_dialect()().create_connect_args(url)
|
|
assert url.drivername == "postgresql+asyncpg"
|
|
assert kwargs["user"] == "us@er:name"
|
|
assert kwargs["password"] == "p@ss/word:%"
|
|
assert kwargs["target_session_attrs"] == "read-write"
|
|
assert "sslmode" not in kwargs
|
|
if mode is None:
|
|
assert "ssl" not in kwargs
|
|
else:
|
|
assert kwargs["ssl"] == mode
|
|
assert config.postgres_url == uri
|
|
assert make_url(config.app_sync_sqlalchemy_url).query == make_url(uri).query
|
|
assert asyncpg_test_url(uri) == config.app_sqlalchemy_url
|
|
|
|
|
|
@pytest.mark.parametrize("query", ["ssl=require", "ssl=require&sslmode=require"])
|
|
def test_explicit_matching_ssl_options_are_preserved(query):
|
|
config = DatabaseConfig(backend="postgres", postgres_url=f"postgresql://user:password@localhost/test?{query}")
|
|
assert make_url(config.app_sqlalchemy_url).query == {"ssl": "require"}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"query",
|
|
[
|
|
"ssl=require&sslmode=disable",
|
|
"sslmode=invalid",
|
|
"sslmode=",
|
|
"ssl=invalid",
|
|
"ssl=",
|
|
"sslmode=require&sslmode=disable",
|
|
"ssl=require&ssl=disable",
|
|
"channel_binding=require",
|
|
"channel_binding=prefer",
|
|
"channel_binding=disable",
|
|
"channel_binding=invalid",
|
|
"channel_binding=",
|
|
],
|
|
)
|
|
def test_unsupported_or_ambiguous_security_options_are_rejected_without_secrets(query):
|
|
config = DatabaseConfig(backend="postgres", postgres_url=f"postgresql://user:private-password@localhost/test?{query}")
|
|
with pytest.raises(ValueError) as caught:
|
|
_ = config.app_sqlalchemy_url
|
|
assert "private-password" not in str(caught.value)
|
|
assert make_url(config.app_sync_sqlalchemy_url).query == make_url(config.postgres_url).query
|
|
|
|
|
|
def test_asyncpg_url_normalization_is_idempotent():
|
|
from deerflow.persistence.postgres_url import normalize_asyncpg_url
|
|
|
|
normalized = normalize_asyncpg_url("postgres://user:p%40ss@localhost/test?sslmode=verify-full")
|
|
assert normalize_asyncpg_url(normalized) == normalized
|
|
|
|
|
|
@pytest.mark.parametrize("scheme", ["sqlite", "mysql", "postgresql+psycopg"])
|
|
def test_asyncpg_url_rejects_other_drivers(scheme):
|
|
from deerflow.persistence.postgres_url import normalize_asyncpg_url
|
|
|
|
with pytest.raises(ValueError, match="PostgreSQL"):
|
|
normalize_asyncpg_url(f"{scheme}://user:password@localhost/test")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_raw_libpq_url_bootstraps_through_database_config():
|
|
uri = os.environ.get("TEST_POSTGRES_URI")
|
|
if not uri:
|
|
pytest.skip("requires TEST_POSTGRES_URI")
|
|
schema = "asyncpg_url_" + uuid4().hex
|
|
config = DatabaseConfig(backend="postgres", postgres_url=uri, postgres_schema=schema)
|
|
cleanup = create_async_engine(asyncpg_test_url(uri))
|
|
try:
|
|
await engine_mod.init_engine_from_config(config)
|
|
async with engine_mod.get_engine().connect() as conn:
|
|
assert await conn.scalar(text("SELECT current_schema()")) == schema
|
|
assert await conn.scalar(text("SELECT version_num FROM alembic_version"))
|
|
assert await conn.scalar(text("SELECT to_regclass('runs')")) == "runs"
|
|
assert config.postgres_url == uri
|
|
finally:
|
|
await engine_mod.close_engine()
|
|
async with cleanup.begin() as conn:
|
|
await conn.execute(text(f'DROP SCHEMA IF EXISTS "{schema}" CASCADE'))
|
|
await cleanup.dispose()
|