From ca2adf999a4ae3a60a3a4de1bc8d0af0af3385ec Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 19 Sep 2026 13:17:35 +0300 Subject: [PATCH 1/3] test: report a bare docs/adr/NNNN citation as unresolved --- tests/test_adr_citations.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/test_adr_citations.py b/tests/test_adr_citations.py index 760f6f1..7f7706c 100644 --- a/tests/test_adr_citations.py +++ b/tests/test_adr_citations.py @@ -7,7 +7,7 @@ _REPO_ROOT: typing.Final = pathlib.Path(__file__).resolve().parent.parent _ADR_DIR: typing.Final = "docs/adr/" -_CITATION: typing.Final = re.compile(r"docs/adr/\d{4}-[a-z0-9-]+\.md") +_CITATION: typing.Final = re.compile(r"docs/adr/\d{4}(?:-[a-z0-9-]+\.md)?") _UNWALKED_DIR: typing.Final = "node_modules" @@ -45,6 +45,8 @@ def test_every_adr_path_cited_from_python_resolves() -> None: offline link gate reads Markdown only, so a path in a docstring, a comment or a guard message is otherwise checked by nothing, and an `INVARIANT:` docstring that names its ADR silently loses the rationale the test depends on. A user who trips a guard is handed a link to follow. + A bare `docs/adr/NNNN` is reported as well: it names no file, so it would survive the same + rename or drop unnoticed and point at whatever record holds that number next. """ unresolved = unresolved_citations(_REPO_ROOT) @@ -64,6 +66,18 @@ def test_a_citation_of_a_missing_adr_is_reported_with_its_citing_file(tmp_path: assert unresolved_citations(tmp_path) == [("pkg/mod.py", f"{_ADR_DIR}9999-missing.md")] +def test_a_short_form_citation_is_reported_even_when_the_adr_exists(tmp_path: pathlib.Path) -> None: + """`docs/adr/NNNN` with no slug names nothing on disk, so a rename or a drop never breaks it.""" + (tmp_path / _ADR_DIR).mkdir(parents=True) + (tmp_path / _ADR_DIR / "0002-kept.md").write_text("# kept\n", encoding="utf-8") + (tmp_path / "pkg").mkdir() + (tmp_path / "pkg" / "mod.py").write_text( + f'"""Argued in {_ADR_DIR}0002 and {_ADR_DIR}0002-kept.md."""\n', encoding="utf-8" + ) + + assert unresolved_citations(tmp_path) == [("pkg/mod.py", f"{_ADR_DIR}0002")] + + def test_a_citation_split_across_adjacent_string_literals_is_found(tmp_path: pathlib.Path) -> None: """Python joins adjacent literals at parse time, which is what the `nack` guard message relies on.""" (tmp_path / "guard.py").write_text( From 3729362254ae44b6c5028afba4111004bf6c39da Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 19 Sep 2026 13:39:56 +0300 Subject: [PATCH 2/3] fix: implement the subscriber protocol members ty 0.0.82 reports as abstract TimersParamsStorage inherits register_subscriber from the LoggerParamsStorage protocol without implementing it; implement it the way faststream's own storages do, widening the channel column. TimersSubscriber lacked __aiter__; it now raises NotImplementedError like get_one. --- faststream_redis_timers/broker.py | 3 +++ faststream_redis_timers/subscriber/usecase.py | 5 +++++ tests/test_unit.py | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/faststream_redis_timers/broker.py b/faststream_redis_timers/broker.py index e6cc224..fa60d96 100644 --- a/faststream_redis_timers/broker.py +++ b/faststream_redis_timers/broker.py @@ -45,6 +45,9 @@ class TimersParamsStorage(DefaultLoggerStorage): __max_msg_id_ln = -1 _max_channel_name = 7 + def register_subscriber(self, params: dict[str, typing.Any]) -> None: + self._max_channel_name = max(self._max_channel_name, len(params.get("channel", ""))) + def get_logger(self, *, context: "ContextRepo") -> LoggerProto: if logger := self._get_logger_ref(): return logger diff --git a/faststream_redis_timers/subscriber/usecase.py b/faststream_redis_timers/subscriber/usecase.py index e8a76bf..936ca55 100644 --- a/faststream_redis_timers/subscriber/usecase.py +++ b/faststream_redis_timers/subscriber/usecase.py @@ -182,6 +182,11 @@ async def get_one(self, *, timeout: float = 5.0) -> typing.NoReturn: msg = "TimersBroker does not support get_one()" raise NotImplementedError(msg) + @override + def __aiter__(self) -> typing.NoReturn: + msg = "TimersBroker does not support iteration" + raise NotImplementedError(msg) + def _make_response_publisher( self, message: "StreamMessage[TimerMessage]", # noqa: ARG002 diff --git a/tests/test_unit.py b/tests/test_unit.py index 1e4822f..ba80aec 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -136,6 +136,16 @@ def test_params_storage_get_logger_is_cached() -> None: assert logger1 is logger2 +def test_params_storage_register_subscriber_widens_the_channel_column() -> None: + logging.getLogger("faststream.access.timers").handlers.clear() + storage = TimersParamsStorage() + storage.register_subscriber({"channel": "billing-reminders"}) + storage.get_logger(context=MagicMock()) + formatter = logging.getLogger("faststream.access.timers").handlers[0].formatter + assert formatter is not None + assert "%(channel)-17s" in formatter._fmt # noqa: SLF001 + + # --- Subscriber.get_one raises --- @@ -146,6 +156,14 @@ async def test_subscriber_get_one_raises() -> None: await sub.get_one() +async def test_subscriber_iteration_raises() -> None: + broker = TimersBroker() + sub = broker.subscriber("topic") + with pytest.raises(NotImplementedError): + async for _ in sub: # pragma: no cover - never yields + pass + + # --- Publisher.request raises --- From 210e31c7f174efbefa17f1f901445460e4f9f219 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 19 Sep 2026 13:40:43 +0300 Subject: [PATCH 3/3] test: narrow the formatter string before asserting on it --- tests/test_unit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_unit.py b/tests/test_unit.py index ba80aec..a784a9e 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -143,7 +143,7 @@ def test_params_storage_register_subscriber_widens_the_channel_column() -> None: storage.get_logger(context=MagicMock()) formatter = logging.getLogger("faststream.access.timers").handlers[0].formatter assert formatter is not None - assert "%(channel)-17s" in formatter._fmt # noqa: SLF001 + assert "%(channel)-17s" in (formatter._fmt or "") # noqa: SLF001 # --- Subscriber.get_one raises ---