"""api и worker в одном процессе без RabbitMQ и Redis: Redis — fakeredis, отправка в очередь подменена доставкой сообщения прямо в WindowPool воркера (tests/unit, tests/browser).""" import asyncio import json from contextlib import contextmanager import fakeredis from fastapi.testclient import TestClient import main from db import clients from db.engine import connect as connect_db from db.journal import Journal from tasks import store as task_store from tasks.broker import RUN_TASK from worker import windows WORKER_ID = "w1" class Harness: def __init__(self, store, pool, redis_sync): self.store = store self.pool = pool self.redis = redis_sync self.messages = [] self.outcomes = [] # True — сообщение выполняется сразу при отправке; False — копится в messages до deliver() self.auto = True self.client: TestClient | None = None async def dispatch(self, message): self.messages.append(message) if self.auto: await self.deliver(message) async def deliver(self, message): if message.actor_name == RUN_TASK: self.outcomes.append(await self.pool.handle(*message.args)) else: await self.pool.close(*message.args) await self.store.heartbeat(WORKER_ID, self.pool.info()) def deliver_pending(self): pending, self.messages = self.messages, [] for message in pending: self.client.portal.call(self.deliver, message) def heartbeat(self, **info): self.redis.set(f"{task_store.PREFIX}:worker:{WORKER_ID}", json.dumps(self.pool.info() | info)) def call(self, fn, *args): return self.client.portal.call(fn, *args) def make_client_key(db, name="test") -> str: async def create(): await db.init() return (await clients.create_client(db, name))[1] key = asyncio.run(create()) asyncio.run(db.close()) # соединения sqlite привязаны к циклу asyncio.run — закрываем, приложение откроет свои return key @contextmanager def run_env(monkeypatch, tmp_path, slots=2): server = fakeredis.FakeServer() store = task_store.TaskStore(fakeredis.aioredis.FakeRedis(server=server, decode_responses=True)) db = connect_db(f"sqlite+aiosqlite:///{tmp_path / 'robotex.db'}") key = make_client_key(db) pool = windows.WindowPool(store, WORKER_ID, slots=slots, journal=Journal(db)) harness = Harness(store, pool, fakeredis.FakeRedis(server=server, decode_responses=True)) harness.db, harness.api_key = db, key # профили — в отдельном каталоге: в tmp_path ещё и файл базы profiles = tmp_path / "profiles" profiles.mkdir() harness.profiles = profiles monkeypatch.setattr(windows, "USER_DATA_ROOT", profiles) monkeypatch.setattr(main, "store", store) monkeypatch.setattr(main, "database", db) monkeypatch.setattr(main, "dispatch", harness.dispatch) harness.heartbeat() with TestClient(main.app, headers={"X-API-Key": key}) as client: harness.client = client yield harness client.portal.call(pool.close_all)