"""api и worker в одном процессе без RabbitMQ и Redis: Redis — fakeredis, отправка в очередь подменена доставкой сообщения прямо в WindowPool воркера (tests/unit, tests/browser).""" import json from contextlib import contextmanager import fakeredis from fastapi.testclient import TestClient import main 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) @contextmanager def run_env(monkeypatch, tmp_path, slots=2): server = fakeredis.FakeServer() store = task_store.TaskStore(fakeredis.aioredis.FakeRedis(server=server, decode_responses=True)) pool = windows.WindowPool(store, WORKER_ID, slots=slots) harness = Harness(store, pool, fakeredis.FakeRedis(server=server, decode_responses=True)) monkeypatch.setattr(windows, "USER_DATA_ROOT", tmp_path) monkeypatch.setattr(main, "store", store) monkeypatch.setattr(main, "dispatch", harness.dispatch) harness.heartbeat() with TestClient(main.app) as client: harness.client = client yield harness client.portal.call(pool.close_all)