Files

116 lines
3.6 KiB
Python

import asyncio
import json
from decimal import Decimal
import aio_pika
from faststream.exceptions import RejectMessage
from faststream.rabbit import Channel, RabbitBroker
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from app.messaging import (
DEAD_QUEUE,
PAYMENTS_EXCHANGE,
PAYMENTS_QUEUE,
PAYMENTS_ROUTING_KEY,
declare_topology,
)
from app.models import Currency, OutboxEvent
from app.outbox import OutboxPublisher
from app.payments import create_payment
from app.schemas import PaymentCreate
async def wait_for_message(
queue: aio_pika.abc.AbstractQueue,
) -> aio_pika.abc.AbstractIncomingMessage:
async with asyncio.timeout(5):
while True:
message = await queue.get(fail=False)
if message is not None:
return message
await asyncio.sleep(0.05)
async def test_rejected_payment_message_is_dead_lettered(rabbitmq_url: str) -> None:
broker = RabbitBroker(
rabbitmq_url,
default_channel=Channel(publisher_confirms=True, on_return_raises=True),
)
@broker.subscriber(PAYMENTS_QUEUE, PAYMENTS_EXCHANGE)
async def reject_message(_: dict[str, str]) -> None:
raise RejectMessage()
await broker.start()
connection = await aio_pika.connect_robust(rabbitmq_url)
try:
await declare_topology(broker)
payload = {"payment_id": "00000000-0000-0000-0000-000000000001"}
await broker.publish(
payload,
queue=PAYMENTS_ROUTING_KEY,
exchange=PAYMENTS_EXCHANGE,
persist=True,
)
channel = await connection.channel()
dead_queue = await channel.declare_queue(DEAD_QUEUE.name, passive=True)
dead_message = await wait_for_message(dead_queue)
assert json.loads(dead_message.body) == payload
await dead_message.ack()
finally:
await connection.close()
await broker.stop()
async def test_outbox_publishes_to_real_payments_queue(
rabbitmq_url: str,
database: async_sessionmaker[AsyncSession],
) -> None:
broker = RabbitBroker(
rabbitmq_url,
default_channel=Channel(publisher_confirms=True, on_return_raises=True),
)
await broker.start()
connection = await aio_pika.connect_robust(rabbitmq_url)
try:
await declare_topology(broker)
channel = await connection.channel()
queue = await channel.declare_queue(PAYMENTS_QUEUE.name, passive=True)
await queue.purge()
async with database() as session:
payment = await create_payment(
session,
PaymentCreate(
amount=Decimal("14.50"),
currency=Currency.RUB,
description="Real RabbitMQ outbox test",
webhook_url="https://example.com/hook",
),
"real-rabbit-outbox",
)
publisher = OutboxPublisher(
database,
broker,
batch_size=10,
poll_interval=0.01,
publish_timeout=5,
)
assert await publisher.publish_batch() == 1
message = await queue.get(timeout=5, fail=True)
assert json.loads(message.body) == {"payment_id": str(payment.id)}
await message.ack()
async with database() as session:
event = await session.scalar(
select(OutboxEvent).where(OutboxEvent.aggregate_id == payment.id)
)
assert event is not None
assert event.published_at is not None
finally:
await connection.close()
await broker.stop()