diff --git a/tests/integration/persistence/test_codex_auth.py b/tests/integration/persistence/test_codex_auth.py index f0aae879a..5ce5662a0 100644 --- a/tests/integration/persistence/test_codex_auth.py +++ b/tests/integration/persistence/test_codex_auth.py @@ -298,12 +298,13 @@ async def test_device_replacement_expiry_and_idempotent_cancel(auth): async def test_device_accepts_issuer_iso_expiry(auth): from datetime import datetime, timezone - expires = datetime.fromtimestamp(time.time() + 600, timezone.utc).isoformat().replace('+00:00', 'Z') + expected_expiry = int(time.time()) + 600 + expires = datetime.fromtimestamp(expected_expiry, timezone.utc).isoformat().replace('+00:00', 'Z') auth._post = AsyncMock( return_value=httpx.Response(200, json={'device_auth_id': 'device', 'user_code': 'CODE', 'expires_at': expires}) ) result = await auth.start(context(), 'p') - assert time.time() < result['expires_at'] < time.time() + 900 + assert result['expires_at'] == expected_expiry @pytest.mark.asyncio diff --git a/tests/integration/persistence/test_workspace_migration.py b/tests/integration/persistence/test_workspace_migration.py index 351b86cfd..3987ed524 100644 --- a/tests/integration/persistence/test_workspace_migration.py +++ b/tests/integration/persistence/test_workspace_migration.py @@ -166,10 +166,14 @@ async def test_workspace_upgrade_repairs_ownerless_existing_local_workspace(lega async with legacy_engine.connect() as conn: workspace = ( - await conn.execute( - sa.text("SELECT uuid, created_by_account_uuid FROM workspaces WHERE source = 'local'"), + ( + await conn.execute( + sa.text("SELECT uuid, created_by_account_uuid FROM workspaces WHERE source = 'local'"), + ) ) - ).mappings().one() + .mappings() + .one() + ) membership = (await conn.execute(sa.text('SELECT * FROM workspace_memberships'))).mappings().one() assert workspace['uuid'] == workspace_uuid_from_instance_id('instance_migration_test') @@ -196,7 +200,7 @@ async def test_existing_oss_workspace_is_rekeyed_to_instance_identity(tmp_path): engine = create_async_engine(f'sqlite+aiosqlite:///{tmp_path / "workspace-rekey.db"}') instance_id = 'instance_a711d9e4-0953-443f-a0e9-7dd50193a79f' old_workspace_uuid = '11111111-1111-4111-8111-111111111111' - canonical_uuid = workspace_uuid_from_instance_id(instance_id) + canonical_uuid = 'a711d9e4-0953-443f-a0e9-7dd50193a79f' schema = sa.MetaData() sa.Table( 'metadata', @@ -218,7 +222,15 @@ async def test_existing_oss_workspace_is_rekeyed_to_instance_identity(tmp_path): sa.Column('id', sa.Integer, primary_key=True), sa.Column('workspace_uuid', sa.String(36), sa.ForeignKey('workspaces.uuid'), nullable=False), ) + sa.Table( + 'workspace_metadata', + schema, + sa.Column('workspace_uuid', sa.String(36), sa.ForeignKey('workspaces.uuid'), nullable=False), + sa.Column('key', sa.String(255)), + sa.Column('value', sa.String(255)), + ) async with engine.begin() as conn: + await conn.execute(sa.text('PRAGMA foreign_keys = ON')) await conn.run_sync(schema.create_all) await conn.execute( sa.text("INSERT INTO metadata (key, value) VALUES ('instance_uuid', :value)"), {'value': instance_id} @@ -233,6 +245,14 @@ async def test_existing_oss_workspace_is_rekeyed_to_instance_identity(tmp_path): sa.text('INSERT INTO tenant_rows (id, workspace_uuid) VALUES (1, :uuid)'), {'uuid': old_workspace_uuid}, ) + await conn.execute( + sa.text("INSERT INTO metadata (key, value) VALUES ('oss_workspace_uuid', :value)"), + {'value': old_workspace_uuid}, + ) + await conn.execute( + sa.text("INSERT INTO workspace_metadata VALUES (:uuid, 'migration_probe', 'present')"), + {'uuid': old_workspace_uuid}, + ) await run_alembic_stamp(engine, '0016_support_admin_sessions') await run_alembic_upgrade(engine, 'head') @@ -240,6 +260,13 @@ async def test_existing_oss_workspace_is_rekeyed_to_instance_identity(tmp_path): async with engine.connect() as conn: assert (await conn.execute(sa.text('SELECT uuid FROM workspaces'))).scalar_one() == canonical_uuid assert (await conn.execute(sa.text('SELECT workspace_uuid FROM tenant_rows'))).scalar_one() == canonical_uuid + assert ( + await conn.execute(sa.text('SELECT workspace_uuid FROM workspace_metadata')) + ).scalar_one() == canonical_uuid + assert ( + await conn.execute(sa.text("SELECT value FROM metadata WHERE key = 'oss_workspace_uuid'")) + ).scalar_one() == canonical_uuid + assert (await conn.execute(sa.text('PRAGMA foreign_key_check'))).all() == [] await engine.dispose() @@ -497,51 +524,6 @@ async def test_persistence_startup_preserves_legacy_workspace_membership_with_fo await manager.shutdown() -async def test_oss_workspace_identity_rekeys_fk_graph_and_metadata(tmp_path): - engine = create_async_engine(f'sqlite+aiosqlite:///{tmp_path / "workspace-rekey.db"}') - try: - await _create_legacy_schema(engine) - await run_alembic_stamp(engine, '0008_mcp_resource_prefs') - await run_alembic_upgrade(engine, '0016_support_admin_sessions') - - async with engine.begin() as conn: - old_uuid = await conn.scalar(sa.text("SELECT uuid FROM workspaces WHERE source = 'local'")) - instance_uuid = await conn.scalar(sa.text("SELECT instance_uuid FROM workspaces WHERE source = 'local'")) - assert old_uuid - assert instance_uuid - await conn.execute( - sa.text( - 'INSERT INTO workspace_metadata (workspace_uuid, key, value) ' - "VALUES (:workspace_uuid, 'migration_probe', 'present')" - ), - {'workspace_uuid': old_uuid}, - ) - await conn.execute( - sa.text( - "INSERT INTO metadata (key, value) VALUES ('oss_workspace_uuid', :workspace_uuid) " - 'ON CONFLICT(key) DO UPDATE SET value = excluded.value' - ), - {'workspace_uuid': old_uuid}, - ) - - await run_alembic_upgrade(engine, 'head') - expected_uuid = workspace_uuid_from_instance_id(instance_uuid) - async with engine.connect() as conn: - assert await conn.scalar(sa.text("SELECT uuid FROM workspaces WHERE source = 'local'")) == expected_uuid - assert ( - await conn.scalar( - sa.text("SELECT workspace_uuid FROM workspace_metadata WHERE key = 'migration_probe'") - ) - == expected_uuid - ) - assert ( - await conn.scalar(sa.text("SELECT value FROM metadata WHERE key = 'oss_workspace_uuid'")) - == expected_uuid - ) - finally: - await engine.dispose() - - async def test_persistence_startup_rejects_instance_uuid_drift(tmp_path, monkeypatch): engine = create_async_engine(f'sqlite+aiosqlite:///{tmp_path / "instance-drift.db"}') try: diff --git a/tests/unit_tests/core/test_boot.py b/tests/unit_tests/core/test_boot.py index 03c82a732..11319eee3 100644 --- a/tests/unit_tests/core/test_boot.py +++ b/tests/unit_tests/core/test_boot.py @@ -36,6 +36,8 @@ async def test_make_app_shuts_down_partially_built_application(monkeypatch): @pytest.mark.asyncio async def test_main_signal_handler_handles_sigint_before_app_created(monkeypatch): captured_handler = {} + print_exc = Mock() + monkeypatch.setattr(boot.traceback, 'print_exc', print_exc) def fake_signal(sig, handler): captured_handler[sig] = handler @@ -48,6 +50,8 @@ async def test_main_signal_handler_handles_sigint_before_app_created(monkeypatch await boot.main(SimpleNamespace()) + print_exc.assert_not_called() + @pytest.mark.asyncio async def test_main_signal_handler_disposes_created_app(monkeypatch): diff --git a/tests/unit_tests/core/test_bootutils_log.py b/tests/unit_tests/core/test_bootutils_log.py index fd01eb9c2..e785b7b78 100644 --- a/tests/unit_tests/core/test_bootutils_log.py +++ b/tests/unit_tests/core/test_bootutils_log.py @@ -10,14 +10,10 @@ from __future__ import annotations import logging import os -import re import langbot.pkg.core.bootutils.log as logmod from langbot.pkg.core.bootutils.log import DailyGroupedRotatingFileHandler -# Mirror of the cleanup pattern in api/http/service/maintenance.py. -MAINTENANCE_LOG_FILE_PATTERN = re.compile(r'^langbot-(\d{4}-\d{2}-\d{2})\.log(?:\.\d+)?$') - def _listing(directory): return sorted(os.listdir(directory)) @@ -105,6 +101,8 @@ class TestDailyGroupedRotatingFileHandler: assert f'langbot-{day}.log' in files def test_all_filenames_match_maintenance_cleanup_pattern(self, tmp_path, monkeypatch): + from langbot.pkg.api.http.service.maintenance import LOG_FILE_PATTERN + box = {'date': '2026-06-08'} self._patch_date(monkeypatch, box) @@ -117,4 +115,4 @@ class TestDailyGroupedRotatingFileHandler: handler.close() for name in _listing(tmp_path): - assert MAINTENANCE_LOG_FILE_PATTERN.match(name), name + assert LOG_FILE_PATTERN.match(name), name diff --git a/tests/unit_tests/core/test_stage.py b/tests/unit_tests/core/test_stage.py index e9b33fb4b..795f52ef6 100644 --- a/tests/unit_tests/core/test_stage.py +++ b/tests/unit_tests/core/test_stage.py @@ -42,11 +42,11 @@ class TestStageClassDecorator: with isolated_sys_modules(mocks): from langbot.pkg.core.stage import stage_class - @stage_class('TestStage') class TestStage: value = 42 - # Class attributes should be preserved + decorated = stage_class('TestStage')(TestStage) + assert decorated is TestStage assert TestStage.value == 42 def test_stage_class_multiple_stages(self): diff --git a/tests/unit_tests/persistence/test_bot_plugin_processor_migration.py b/tests/unit_tests/persistence/test_bot_plugin_processor_migration.py index 4e3e4f0ad..b29f2152a 100644 --- a/tests/unit_tests/persistence/test_bot_plugin_processor_migration.py +++ b/tests/unit_tests/persistence/test_bot_plugin_processor_migration.py @@ -46,4 +46,29 @@ def test_migrate_duplicate_routes_and_preserve_primary_and_disabled(column_exist assert conn.execute(sa.select(migrated)).mappings().one() == row migration.downgrade() assert 'plugin_processors' not in {c['name'] for c in sa.inspect(conn).get_columns('bots')} + restored = sa.Table('bots', sa.MetaData(), autoload_with=conn) + bindings = conn.execute(sa.select(restored.c.event_bindings)).scalar_one() + assert bindings == [ + primary, + { + 'id': 'plugin_processor:one', + 'event_pattern': '*', + 'target_type': 'event_processor', + 'target_uuid': 'one', + 'enabled': True, + 'filters': [], + 'priority': 0, + 'order': 1, + }, + { + 'id': 'plugin_processor:two', + 'event_pattern': '*', + 'target_type': 'event_processor', + 'target_uuid': 'two', + 'enabled': False, + 'filters': [], + 'priority': 0, + 'order': 2, + }, + ] engine.dispose() diff --git a/tests/unit_tests/persistence/test_mgr_methods.py b/tests/unit_tests/persistence/test_mgr_methods.py index 0c75cf2d4..0182b7eea 100644 --- a/tests/unit_tests/persistence/test_mgr_methods.py +++ b/tests/unit_tests/persistence/test_mgr_methods.py @@ -50,8 +50,8 @@ class TestExecuteAsync: # Execute a simple select await mgr.execute_async(sqlalchemy.select(1)) - mock_conn.execute.assert_called_once() - mock_conn.commit.assert_called_once() + mock_conn.execute.assert_awaited_once() + mock_conn.commit.assert_awaited_once() @pytest.mark.asyncio async def test_execute_async_returns_result(self): diff --git a/tests/unit_tests/persistence/test_runner_state_migration.py b/tests/unit_tests/persistence/test_runner_state_migration.py index 82e12282a..f016d09b6 100644 --- a/tests/unit_tests/persistence/test_runner_state_migration.py +++ b/tests/unit_tests/persistence/test_runner_state_migration.py @@ -39,4 +39,6 @@ def test_two_populated_tables_fail_without_discarding_either(): with pytest.raises(RuntimeError, match='Both .* contain state'): migration.upgrade() assert set(sa.inspect(conn).get_table_names()) == {'agent_runner_state', 'runner_state'} + assert conn.execute(sa.text('SELECT id FROM agent_runner_state')).all() == [(1,)] + assert conn.execute(sa.text('SELECT id FROM runner_state')).all() == [(1,)] engine.dispose() diff --git a/tests/unit_tests/persistence/test_serialize_model.py b/tests/unit_tests/persistence/test_serialize_model.py index bbab59c67..e90b3d337 100644 --- a/tests/unit_tests/persistence/test_serialize_model.py +++ b/tests/unit_tests/persistence/test_serialize_model.py @@ -81,7 +81,7 @@ class TestSerializeModel: result = mgr.serialize_model(TestModel, instance) - assert '2024-01-15' in result['created_at'] + assert result['created_at'] == '2024-01-15T10:30:45+00:00' assert isinstance(result['created_at'], str) def test_serialize_none_datetime(self):