Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions faststream_redis_timers/broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ class TimersBroker(
_subscribers: list[TimersSubscriber]
_publishers: list[TimersPublisher]

@property
@override
def subscribers(self) -> list[TimersSubscriber]:
return typing.cast("list[TimersSubscriber]", super().subscribers)

def __init__( # noqa: PLR0913
self,
client: "RedisClient | None" = None,
Expand Down
8 changes: 4 additions & 4 deletions faststream_redis_timers/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ def create_publisher_fake_subscriber(
publisher: TimersPublisher,
) -> tuple[TimersSubscriber, bool]:
subscriber: TimersSubscriber | None = None
for handler in broker._subscribers: # noqa: SLF001
# `broker.subscribers`, not `_subscribers`: the latter omits every router's.
for handler in broker.subscribers:
if handler._config.full_topic == publisher.config.full_topic: # noqa: SLF001
subscriber = handler
break
if subscriber is None:
is_real = False
subscriber = broker.subscriber(publisher.config.topic)
subscriber = broker.subscriber(publisher.config.full_topic)
else:
is_real = True
return subscriber, is_real
Expand Down Expand Up @@ -123,8 +124,7 @@ async def publish(self, cmd: TimerPublishCommand) -> None:
data=payload,
)
for handler in self.broker.subscribers:
sub = typing.cast("TimersSubscriber", handler)
if sub._config.full_topic == topic: # noqa: SLF001
if handler._config.full_topic == topic: # noqa: SLF001
await handler.process_message(msg)

async def cancel(self, full_topic: str, timer_id: str) -> None:
Expand Down
47 changes: 46 additions & 1 deletion tests/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import inspect
import logging
import warnings
from datetime import UTC, datetime
from datetime import UTC, datetime, timedelta
from unittest.mock import AsyncMock, MagicMock, patch

import anyio
Expand Down Expand Up @@ -557,6 +557,51 @@ def test_create_publisher_fake_subscriber_is_instance_method() -> None:
assert next(iter(sig.parameters)) == "self"


async def test_publisher_is_wired_to_a_subscriber_declared_on_a_prefixed_router() -> None:
"""INVARIANT: a publisher is wired to the real subscriber wherever that subscriber was declared.

`create_publisher_fake_subscriber` decides that by scanning the broker's subscribers, and
`broker._subscribers` holds only endpoints registered directly on the broker — a router's live
behind the `subscribers` property. Scanning the private list finds nothing for a router, so the
publisher is wired to a freshly built fake instead; behind a prefix that fake sits on the
unprefixed topic, which nothing publishes to, and the mock stays silent on a publish that
happened. Registering the fallback under the raw `topic` rather than `full_topic` breaks it the
same way.
"""
broker = TimersBroker()
router = TimersRouter(prefix="app:")
sub = router.subscriber("reminders")

@sub
async def handle(body: str) -> None: ...

publisher = router.publisher("reminders")
broker.include_router(router)

async with TestTimersBroker(broker):
await publisher.publish("ping", activate_in=timedelta(0))
publisher.mock.assert_called_once_with("ping")


@pytest.mark.parametrize("prefix", ["", "app:"], ids=["no-prefix", "prefixed"])
async def test_router_publisher_does_not_add_a_second_subscriber(prefix: str) -> None:
"""A publisher that already has a subscriber on its topic reuses it rather than building a fake."""
broker = TimersBroker()
router = TimersRouter(prefix=prefix)
sub = router.subscriber("reminders")

@sub
async def handle(body: str) -> None: ...

router.publisher("reminders")
broker.include_router(router)

async with TestTimersBroker(broker):
topics = [s._config.full_topic for s in broker.subscribers] # noqa: SLF001

assert topics == [f"{prefix}reminders"]


# --- TimerStore ---


Expand Down
Loading