102 lines
4.2 KiB
Python
102 lines
4.2 KiB
Python
from dataclasses import replace
|
|
from uuid import uuid4
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from scripts import run_operational_worker as worker
|
|
from scripts import sync_peripheral_work_orders as sync
|
|
|
|
|
|
@pytest.fixture
|
|
def anyio_backend():
|
|
return "asyncio"
|
|
|
|
|
|
@pytest.fixture
|
|
def settings():
|
|
projects = (str(uuid4()), str(uuid4()))
|
|
return worker.WorkerSettings("http://server.test", "http://agent.test", projects[0],
|
|
bearer="TEST_ONLY", project_ids=projects)
|
|
|
|
|
|
def test_explicit_project_list_overrides_single_project_and_deduplicates(monkeypatch, settings):
|
|
monkeypatch.setattr(worker, "load_dotenv", lambda *args, **kwargs: None)
|
|
monkeypatch.setenv("OPERATIONAL_WORKER_PROJECT_IDS", ", ".join(settings.project_ids + settings.project_ids[:1]))
|
|
monkeypatch.setenv("OPERATIONAL_WORKER_PROJECT_ID", "ignored-legacy-value")
|
|
monkeypatch.setenv("OPERATIONAL_WORKER_BEARER", "TEST_ONLY")
|
|
parsed = worker.WorkerSettings.from_environment()
|
|
assert parsed.project_ids == settings.project_ids
|
|
assert tuple(scope.project_id for scope in parsed.project_scopes()) == settings.project_ids
|
|
assert all(not scope.project_ids for scope in parsed.project_scopes())
|
|
monkeypatch.setenv("OPERATIONAL_WORKER_PROJECT_IDS", settings.project_ids[0] + ",invalid")
|
|
with pytest.raises(ValueError):
|
|
worker.WorkerSettings.from_environment()
|
|
|
|
|
|
def test_existing_single_project_configuration_remains_supported(monkeypatch, settings):
|
|
monkeypatch.setattr(worker, "load_dotenv", lambda *args, **kwargs: None)
|
|
monkeypatch.delenv("OPERATIONAL_WORKER_PROJECT_IDS", raising=False)
|
|
monkeypatch.setenv("OPERATIONAL_WORKER_PROJECT_ID", settings.project_id)
|
|
monkeypatch.setenv("OPERATIONAL_WORKER_BEARER", "TEST_ONLY")
|
|
assert worker.WorkerSettings.from_environment().project_scopes()[0].project_id == settings.project_id
|
|
assert replace(settings, project_ids=()).project_scopes() == (replace(settings, project_ids=()),)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_multi_project_worker_reconciles_other_projects_when_one_fails(monkeypatch, settings):
|
|
seen = []
|
|
|
|
class FakeWorker:
|
|
def __init__(self, scope, client):
|
|
self.scope = scope
|
|
|
|
async def tick(self):
|
|
seen.append(self.scope.project_id)
|
|
if self.scope.project_id == settings.project_ids[0]:
|
|
raise httpx.ConnectError("test failure")
|
|
return 0
|
|
|
|
monkeypatch.setattr(worker.WorkerSettings, "from_environment", lambda: settings)
|
|
monkeypatch.setattr(worker, "OperationalWorker", FakeWorker)
|
|
with pytest.raises(SystemExit) as result:
|
|
await worker.run(once=True)
|
|
assert result.value.code == 1
|
|
assert tuple(seen) == settings.project_ids
|
|
|
|
|
|
@pytest.mark.anyio
|
|
@pytest.mark.parametrize("once", [True, False])
|
|
async def test_sync_entrypoint_keeps_project_headers_and_cursors_separate(monkeypatch, settings, once):
|
|
seen = []
|
|
|
|
async def check_scope(client, scope, url, credentials):
|
|
seen.append((scope.project_id, (await credentials.headers(client))["X-Project-Id"]))
|
|
return 0
|
|
|
|
class FakeSynchronizer:
|
|
def __init__(self, client, scope, url, credentials):
|
|
self.args = client, scope, url, credentials
|
|
|
|
async def serve(self, interval):
|
|
return await check_scope(*self.args)
|
|
|
|
monkeypatch.setattr(worker.WorkerSettings, "from_environment", lambda: settings)
|
|
monkeypatch.setenv("OPERATIONAL_ACTION_PERIPHERAL_URL", "http://peripheral.test")
|
|
monkeypatch.setattr(sync, "sync_once", check_scope)
|
|
monkeypatch.setattr(sync, "PeripheralWorkOrderSynchronizer", FakeSynchronizer)
|
|
await sync.run(once=once)
|
|
assert seen == [(project, project) for project in settings.project_ids]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_project_workers_have_independent_tokens_sessions_and_message_cursors(settings):
|
|
async with httpx.AsyncClient() as client:
|
|
first, second = [worker.OperationalWorker(scope, client) for scope in settings.project_scopes()]
|
|
first.credentials._token = "FIRST_TEST_TOKEN"
|
|
first._agent_sessions["run-1"] = "session-1"
|
|
first._message_cursor = "cursor-1"
|
|
assert second.credentials._token == ""
|
|
assert second._agent_sessions == {}
|
|
assert second._message_cursor is None
|