From 12d059d6587834fa8e7fb1b8579e5491c8d4a4f4 Mon Sep 17 00:00:00 2001 From: Ai Vong Date: Fri, 11 Sep 2026 10:53:43 -0500 Subject: [PATCH] test: reuse testcontainers harness for condenser reconcile tests Port the org condenser reconcile tests off the bespoke Postgres harness (a GitHub service container + POSTGRES_TEST_DATABASE_URL, with the schema hand-built via Org table metadata) and onto the existing testcontainers harness fixtures in tests/unit/conftest.py. The async_session_maker fixture already provides a per-test database cloned from the alembic-migrated template, so the tests now exercise the real schema (including migration 108 server_default on org.agent_settings) instead of a model-derived one. They run under the existing py-tests workflow with no new CI, service container, or POSTGRES_TEST_DATABASE_URL wiring. Verified locally against testcontainers postgres:16: all 3 tests pass. Follow-ups that need the workflow OAuth scope (this automation cannot push workflow files): delete .github/workflows/postgres-integration-tests.yml, and close PR #369 which only worked around the old harness collision. Co-authored-by: openhands --- pytest.ini | 2 - .../test_org_condenser_reconciliation.py} | 52 ++++++------------- 2 files changed, 15 insertions(+), 39 deletions(-) rename tests/{integration/test_org_condenser_reconciliation_postgres.py => unit/test_org_condenser_reconciliation.py} (77%) diff --git a/pytest.ini b/pytest.ini index 38cce22701..3e63fa281b 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,5 +4,3 @@ pythonpath = . addopts = -p no:warnings -p no:ddtrace -p no:ddtrace.pytest_bdd -p no:ddtrace.pytest_benchmark asyncio_mode = auto asyncio_default_fixture_loop_scope = function -markers = - postgresql: tests that require a PostgreSQL database diff --git a/tests/integration/test_org_condenser_reconciliation_postgres.py b/tests/unit/test_org_condenser_reconciliation.py similarity index 77% rename from tests/integration/test_org_condenser_reconciliation_postgres.py rename to tests/unit/test_org_condenser_reconciliation.py index 3f0af3855b..efce240ccc 100644 --- a/tests/integration/test_org_condenser_reconciliation_postgres.py +++ b/tests/unit/test_org_condenser_reconciliation.py @@ -1,37 +1,15 @@ -import os import uuid from datetime import UTC, datetime, timedelta import pytest from sqlalchemy import select -from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine +from sqlalchemy.ext.asyncio import async_sessionmaker from storage.org import Org from storage.org_store import OrgStore -pytestmark = pytest.mark.postgresql - -@pytest.fixture -async def postgres_session_maker(): - dsn = os.getenv('POSTGRES_TEST_DATABASE_URL') - if not dsn: - pytest.skip('POSTGRES_TEST_DATABASE_URL is required') - - engine = create_async_engine(dsn, echo=False) - async with engine.begin() as conn: - await conn.run_sync(Org.__table__.drop, checkfirst=True) - await conn.run_sync(Org.__table__.create) - - try: - yield async_sessionmaker(engine, expire_on_commit=False) - finally: - async with engine.begin() as conn: - await conn.run_sync(Org.__table__.drop, checkfirst=True) - await engine.dispose() - - -async def _insert_orgs(session_maker, rows): +async def _insert_orgs(session_maker: async_sessionmaker, rows) -> None: async with session_maker() as session: async with session.begin(): for name, agent_settings in rows: @@ -44,7 +22,7 @@ async def _insert_orgs(session_maker, rows): ) -async def _settings_by_name(session_maker): +async def _settings_by_name(session_maker: async_sessionmaker) -> dict: async with session_maker() as session: rows = (await session.execute(select(Org.name, Org.agent_settings))).all() return {name: settings for name, settings in rows} @@ -52,10 +30,10 @@ async def _settings_by_name(session_maker): @pytest.mark.asyncio async def test_reconcile_non_overwrite_updates_only_unset_applicable_rows( - postgres_session_maker, + async_session_maker: async_sessionmaker, ): await _insert_orgs( - postgres_session_maker, + async_session_maker, [ ('missing-condenser', {'agent_kind': 'openhands'}), ('json-null-condenser', {'condenser': None}), @@ -69,7 +47,7 @@ async def test_reconcile_non_overwrite_updates_only_unset_applicable_rows( ], ) - async with postgres_session_maker() as session: + async with async_session_maker() as session: async with session.begin(): result = await OrgStore.reconcile_applicable_org_condenser_max_tokens( session, @@ -82,7 +60,7 @@ async def test_reconcile_non_overwrite_updates_only_unset_applicable_rows( assert result.skipped_condenser_variant_count == 1 assert result.malformed_repaired_count == 2 - settings = await _settings_by_name(postgres_session_maker) + settings = await _settings_by_name(async_session_maker) assert settings['missing-condenser']['condenser']['max_tokens'] == 200000 assert settings['json-null-condenser']['condenser']['max_tokens'] == 200000 assert settings['missing-max-tokens']['condenser']['enabled'] is True @@ -100,10 +78,10 @@ async def test_reconcile_non_overwrite_updates_only_unset_applicable_rows( @pytest.mark.asyncio async def test_reconcile_overwrite_forces_applicable_existing_values( - postgres_session_maker, + async_session_maker: async_sessionmaker, ): await _insert_orgs( - postgres_session_maker, + async_session_maker, [ ('existing-max-tokens', {'condenser': {'max_tokens': 123456}}), ('noop-condenser', {'condenser': {'condenser_kind': 'no_op'}}), @@ -111,7 +89,7 @@ async def test_reconcile_overwrite_forces_applicable_existing_values( ], ) - async with postgres_session_maker() as session: + async with async_session_maker() as session: async with session.begin(): result = await OrgStore.reconcile_applicable_org_condenser_max_tokens( session, @@ -124,7 +102,7 @@ async def test_reconcile_overwrite_forces_applicable_existing_values( assert result.skipped_condenser_variant_count == 1 assert result.malformed_repaired_count == 0 - settings = await _settings_by_name(postgres_session_maker) + settings = await _settings_by_name(async_session_maker) assert settings['existing-max-tokens']['condenser']['max_tokens'] == 200000 assert settings['noop-condenser']['condenser'] == {'condenser_kind': 'no_op'} assert 'condenser' not in settings['acp-agent'] @@ -132,10 +110,10 @@ async def test_reconcile_overwrite_forces_applicable_existing_values( @pytest.mark.asyncio async def test_reconcile_bumps_updated_at_only_for_changed_rows( - postgres_session_maker, + async_session_maker: async_sessionmaker, ): stale = datetime.now(UTC) - timedelta(days=30) - async with postgres_session_maker() as session: + async with async_session_maker() as session: async with session.begin(): session.add_all( [ @@ -154,7 +132,7 @@ async def test_reconcile_bumps_updated_at_only_for_changed_rows( ] ) - async with postgres_session_maker() as session: + async with async_session_maker() as session: async with session.begin(): await OrgStore.reconcile_applicable_org_condenser_max_tokens( session, @@ -162,7 +140,7 @@ async def test_reconcile_bumps_updated_at_only_for_changed_rows( overwrite_existing=False, ) - async with postgres_session_maker() as session: + async with async_session_maker() as session: rows = dict((await session.execute(select(Org.name, Org.updated_at))).all()) assert rows['changed'] > stale