| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980 |
- from __future__ import annotations
- from pathlib import Path
- import pytest
- from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
- from script_build_host.domain.errors import (
- MissionAlreadyOwned,
- MissionFencingTokenStale,
- MissionStopRequested,
- )
- from script_build_host.domain.records import BuildStatus
- from script_build_host.infrastructure.ownership import FencedCommandGate, OwnerLease
- from script_build_host.repositories.legacy_state import SqlAlchemyLegacyBuildStateRepository
- from script_build_host.repositories.sqlalchemy import SqlAlchemyMissionBindingRepository
- async def _bound_build(sessions: async_sessionmaker[AsyncSession]) -> int:
- state = SqlAlchemyLegacyBuildStateRepository(sessions)
- build_id = await state.create(
- execution_id=1,
- topic_build_id=2,
- topic_id=3,
- agent_type="AigcAgent",
- agent_config={},
- data_source_url=None,
- strategies_config={},
- root_trace_id="root-owner-test",
- )
- await SqlAlchemyMissionBindingRepository(sessions).create(
- script_build_id=build_id,
- root_trace_id="root-owner-test",
- input_snapshot_id=1,
- engine_version="test",
- schema_version="test/v1",
- )
- return build_id
- @pytest.mark.asyncio
- async def test_owner_lease_excludes_second_owner_and_grows_epoch(
- database: tuple[AsyncEngine, async_sessionmaker[AsyncSession]], tmp_path: Path
- ) -> None:
- _, sessions = database
- build_id = await _bound_build(sessions)
- first = OwnerLease(sessions, tmp_path / "leases", owner_instance_id="owner-a")
- second = OwnerLease(sessions, tmp_path / "leases", owner_instance_id="owner-b")
- first_token = await first.acquire(build_id)
- with pytest.raises(MissionAlreadyOwned):
- await second.acquire(build_id)
- await first.release()
- second_token = await second.acquire(build_id)
- assert second_token.owner_epoch == first_token.owner_epoch + 1
- with pytest.raises(MissionFencingTokenStale):
- await FencedCommandGate(sessions).verify(first_token)
- await second.release()
- @pytest.mark.asyncio
- async def test_stop_epoch_fences_current_owner_without_taking_process_lease(
- database: tuple[AsyncEngine, async_sessionmaker[AsyncSession]], tmp_path: Path
- ) -> None:
- _, sessions = database
- build_id = await _bound_build(sessions)
- lease = OwnerLease(sessions, tmp_path / "leases", owner_instance_id="owner")
- token = await lease.acquire(build_id)
- gate = FencedCommandGate(sessions)
- await gate.verify(token)
- assert await gate.request_stop(build_id) == token.stop_epoch + 1
- with pytest.raises(MissionStopRequested):
- await gate.verify(token)
- assert (
- await SqlAlchemyLegacyBuildStateRepository(sessions).get_status(build_id)
- is BuildStatus.STOPPING
- )
- await lease.release()
|