Skip to content

TestTimersBroker ignores router-registered subscribers, breaking publisher.mock behind a prefix #86

Description

@lesnik512

What

TestTimersBroker.create_publisher_fake_subscriber scans broker._subscribers to find the real subscriber a publisher should be wired to. That list holds only endpoints registered directly on the broker — upstream's Registrator.subscribers is what includes routers':

@property
def subscribers(self) -> list["SubscriberUsecase[MsgType]"]:
    return [*self._subscribers, *(sub for r in self.routers for sub in r.subscribers)]

So for anything declared on a TimersRouter, the scan finds nothing, is_real comes back False, and the fallback runs:

subscriber = broker.subscriber(publisher.config.topic)

publisher.config.topic is the unprefixed topic, so with a prefixed router the fake lands on a topic nothing publishes to.

Why it matters

is_real=True is what makes upstream mirror the real handler's calls into the publisher's recorder. With a router it is always False, and behind a prefix the fake never fires at all, so publisher.mock records nothing:

broker = TimersBroker()
router = TimersRouter(prefix="app:")
sub = router.subscriber("reminders")

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

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

async with TestTimersBroker(broker):
    await pub.publish("ping", activate_in=timedelta(0))
    pub.mock.assert_called_once_with("ping")
AssertionError: Expected 'mock' to be called once. Called 0 times.

The publish did happen. Anyone testing a prefixed router cannot assert on it.

Observed against faststream 0.7.6 at a011d8c. Three configurations, same script:

declaration broker._subscribers broker.subscribers subscribers after entering the test broker publisher.mock
direct on broker ['reminders'] ['reminders'] ['reminders'] records
router, no prefix [] ['reminders'] ['reminders', 'reminders'] records
router, prefix app: [] ['app:reminders'] ['reminders', 'app:reminders'] silent

The no-prefix row is wrong too — it builds a duplicate subscriber on a topic that already had one — but it happens to keep working, because the fake carries no user handler and the topic still matches. Only the prefixed row fails visibly.

It also leaks into the AsyncAPI document as a stray reminders:PublisherResponseSubscriber channel next to the correct app:reminders:* ones. That was how this surfaced, during #85.

Fix

Scan broker.subscribers rather than broker._subscribers, so router-registered endpoints are visible. The fallback should register the topic the publisher actually resolves to rather than the raw one, or the match should key off full_topic on both sides consistently.

Tests

TDD: a failing test first. The table above is the shape worth locking in — a test that only covers the direct-on-broker case passes today and would have passed throughout.

Not a 0.7.6 regression

Pre-existing. Registrator.subscribers has had the router-aware definition throughout 0.7; nothing in #85 touched this path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingready-for-agentFully specified, ready for an AFK agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions