# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence. """Upgrade announcements: told once, never on the way in, never in a loop.""" from __future__ import annotations from typing import TYPE_CHECKING from signal_bot.announce import ANNOUNCED_KEY, announce_all, announce_to, render from signal_bot.changelog import parse from signal_bot.transport_fake import FakeTransport if TYPE_CHECKING: from signal_bot.store import Store GROUP = "group.one" DOCUMENT = """\ ## 0.0.1 — Linus - druga zmiana ## 0.0.0 — Święty Piotr Apostoł - pierwsza zmiana """ RELEASES = parse(DOCUMENT) async def _announce(store: Store, transport: FakeTransport, version: str = "0.0.1") -> None: await announce_to(transport, store, GROUP, RELEASES, version, "Linus") def test_a_single_release_is_rendered_flat() -> None: """The header already names it; a per-release block would just repeat it.""" message = render("0.0.1", "Linus", RELEASES[1:]) assert message == "Wersja 0.0.1 «Linus» wydana\nChangelog:\n- druga zmiana" def test_a_long_backlog_is_capped_and_says_so() -> None: """A group added years from now must not be handed 200 releases.""" document = "\n\n".join( f"## {v} — Pope{i}\n\n- zmiana {i}" for i, v in enumerate( [ "0.0.0", "0.0.1", "0.0.2", "0.0.3", "0.0.4", "0.0.5", "0.0.6", "0.0.7", "0.1.0", "0.1.1", "0.1.2", "0.1.3", ] ) ) message = render("0.1.3", "Soter", parse(document)) assert "(pominieto 2 starszych wersji)" in message assert "zmiana 0" not in message assert "zmiana 11" in message def test_render_names_the_version_and_lists_every_pending_line() -> None: message = render("0.0.1", "Linus", RELEASES) assert message.startswith("Wersja 0.0.1 «Linus» wydana\nChangelog:") assert "- pierwsza zmiana" in message assert "- druga zmiana" in message async def test_a_group_told_nothing_yet_hears_the_ladder_from_peter( store: Store, transport: FakeTransport ) -> None: """kuhy asked for this explicitly: the joke only lands if Peter is posted.""" await _announce(store, transport) assert len(transport.sent) == 1 message = transport.sent[0].text or "" assert "0.0.0 «Święty Piotr Apostoł»" in message assert "pierwsza zmiana" in message assert "druga zmiana" in message assert store.meta(GROUP, ANNOUNCED_KEY) == "0.0.1" async def test_a_group_already_on_this_version_hears_nothing( store: Store, transport: FakeTransport ) -> None: store.set_meta(GROUP, ANNOUNCED_KEY, "0.0.1") await _announce(store, transport) assert transport.sent == [] async def test_an_upgraded_group_is_told_what_changed( store: Store, transport: FakeTransport ) -> None: store.set_meta(GROUP, ANNOUNCED_KEY, "0.0.0") await _announce(store, transport) assert len(transport.sent) == 1 assert "Wersja 0.0.1 «Linus» wydana" in (transport.sent[0].text or "") assert "druga zmiana" in (transport.sent[0].text or "") assert "pierwsza zmiana" not in (transport.sent[0].text or "") assert store.meta(GROUP, ANNOUNCED_KEY) == "0.0.1" async def test_a_version_with_no_changelog_entry_records_but_says_nothing( store: Store, transport: FakeTransport ) -> None: """An empty announcement is worse than none.""" store.set_meta(GROUP, ANNOUNCED_KEY, "0.0.1") await _announce(store, transport, version="0.0.2") assert transport.sent == [] assert store.meta(GROUP, ANNOUNCED_KEY) == "0.0.2" async def test_the_version_is_recorded_before_the_send(store: Store) -> None: """A crash between the two must not re-announce on every restart.""" class Exploding(FakeTransport): async def send_text(self, text: str, recipient: str) -> None: raise RuntimeError(text, recipient) store.set_meta(GROUP, ANNOUNCED_KEY, "0.0.0") await announce_all(Exploding(), store, [GROUP], RELEASES, "0.0.1", "Linus") assert store.meta(GROUP, ANNOUNCED_KEY) == "0.0.1" async def test_one_failing_group_does_not_silence_the_others(store: Store) -> None: other = "group.two" class RefusingFirst(FakeTransport): async def send_text(self, text: str, recipient: str) -> None: if recipient == GROUP: raise RuntimeError(recipient) await super().send_text(text, recipient) for group in (GROUP, other): store.set_meta(group, ANNOUNCED_KEY, "0.0.0") transport = RefusingFirst() await announce_all(transport, store, [GROUP, other], RELEASES, "0.0.1", "Linus") assert [message.recipient for message in transport.sent] == [other]