|
1 | 1 | import asyncio |
| 2 | +import contextlib |
2 | 3 | import typing |
3 | 4 | import uuid |
4 | 5 |
|
5 | 6 | from aiokafka.admin import AIOKafkaAdminClient, NewTopic |
6 | | -from faststream.kafka import KafkaBroker |
| 7 | +from faststream import ContextRepo |
| 8 | +from faststream.asgi import AsgiFastStream |
| 9 | +from faststream.kafka import KafkaBroker, KafkaRouter |
7 | 10 | from faststream.middlewares import AckPolicy |
8 | 11 |
|
9 | 12 | from faststream_concurrent_aiokafka import ( |
@@ -360,3 +363,69 @@ async def handler_b2(msg: dict[str, int]) -> None: # pragma: no cover |
360 | 363 |
|
361 | 364 | assert replayed_a == [], f"topic_a messages replayed after clean stop: {replayed_a}" |
362 | 365 | assert replayed_b == [], f"topic_b messages replayed after clean stop: {replayed_b}" |
| 366 | + |
| 367 | + |
| 368 | +async def test_middleware_on_router(kafka_bootstrap_servers: str) -> None: |
| 369 | + """Middleware set on a KafkaRouter (not broker-level) routes messages through concurrent handler.""" |
| 370 | + processed: typing.Final[list[dict[str, int]]] = [] |
| 371 | + topic: typing.Final = _topic("router-mw") |
| 372 | + # Plain broker — no broker-level middleware; middleware is scoped to the router |
| 373 | + broker: typing.Final = KafkaBroker(kafka_bootstrap_servers) |
| 374 | + router: typing.Final = KafkaRouter(middlewares=[KafkaConcurrentProcessingMiddleware]) |
| 375 | + |
| 376 | + @router.subscriber(topic, group_id="router-mw-group", auto_offset_reset="earliest", ack_policy=AckPolicy.MANUAL) |
| 377 | + async def handler(msg: dict[str, int]) -> None: |
| 378 | + processed.append(msg) |
| 379 | + |
| 380 | + broker.include_router(router) |
| 381 | + |
| 382 | + await _create_topic(kafka_bootstrap_servers, topic) |
| 383 | + async with broker: |
| 384 | + await broker.start() |
| 385 | + await initialize_concurrent_processing( |
| 386 | + context=broker.context, commit_batch_size=10, commit_batch_timeout_sec=5, concurrency_limit=5 |
| 387 | + ) |
| 388 | + await asyncio.sleep(CONSUMER_READY_SLEEP) |
| 389 | + try: |
| 390 | + await broker.publish({"id": 7}, topic=topic) |
| 391 | + await asyncio.sleep(POLL_SLEEP) |
| 392 | + finally: |
| 393 | + await stop_concurrent_processing(broker.context) |
| 394 | + |
| 395 | + assert len(processed) == 1 |
| 396 | + assert processed[0]["id"] == 7 |
| 397 | + |
| 398 | + |
| 399 | +async def test_asgi_faststream_basic_processing(kafka_bootstrap_servers: str) -> None: |
| 400 | + """AsgiFastStream lifespan initialises and stops concurrent processing correctly.""" |
| 401 | + processed: typing.Final[list[dict[str, int]]] = [] |
| 402 | + topic: typing.Final = _topic("asgi") |
| 403 | + broker: typing.Final = _broker(kafka_bootstrap_servers) |
| 404 | + |
| 405 | + @broker.subscriber(topic, group_id="asgi-group", auto_offset_reset="earliest", ack_policy=AckPolicy.MANUAL) |
| 406 | + async def handler(msg: dict[str, int]) -> None: |
| 407 | + processed.append(msg) |
| 408 | + |
| 409 | + @contextlib.asynccontextmanager |
| 410 | + async def lifespan(_context: ContextRepo) -> typing.AsyncIterator[None]: |
| 411 | + # AsgiFastStream injects its own app-level context, which is separate from |
| 412 | + # broker.context. Use broker.context explicitly so the middleware can find |
| 413 | + # the handler via self.context. |
| 414 | + await initialize_concurrent_processing( |
| 415 | + context=broker.context, commit_batch_size=10, commit_batch_timeout_sec=5, concurrency_limit=5 |
| 416 | + ) |
| 417 | + try: |
| 418 | + yield |
| 419 | + finally: |
| 420 | + await stop_concurrent_processing(broker.context) |
| 421 | + |
| 422 | + app: typing.Final = AsgiFastStream(broker, lifespan=lifespan) |
| 423 | + |
| 424 | + await _create_topic(kafka_bootstrap_servers, topic) |
| 425 | + async with app.start_lifespan_context(): |
| 426 | + await asyncio.sleep(CONSUMER_READY_SLEEP) |
| 427 | + await broker.publish({"id": 42}, topic=topic) |
| 428 | + await asyncio.sleep(POLL_SLEEP) |
| 429 | + |
| 430 | + assert len(processed) == 1 |
| 431 | + assert processed[0]["id"] == 42 |
0 commit comments