mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-23 00:45:25 +08:00
test: strengthen startup and persistence boundary oracles
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -41,11 +41,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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user