mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-11 14:17:31 +01:00
"Wersja 0.1.7 «Callistus I»" told nobody whether Callistus was the fifteenth pope or the hundredth, and the changelog was the one place the ladder is actually read. pope_line() derives both facts from popes.json -- the ordinal, and the `reign` string exactly as the table spells it, so the one reigning pope keeps his single year instead of being padded into a range he never had. It appears in all four places a version is named: the group announcement (header and every block of a backlog), each section of DOCS-changelog.md, /healthz, and the !papiez caption, which now describes the release's pope in the same sentence it uses for every other one. The changelog's copy is checked rather than trusted, like the heading name already was: a test walks the raw file and compares each line against popes.json, so a hand-edited ordinal fails here instead of in a group. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017w1HZ3cZ7neL3rFdQ6VMBA
86 lines
3.1 KiB
Python
86 lines
3.1 KiB
Python
# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence.
|
|
"""/healthz must go red when the bot cannot hear, not just when it is down."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import replace
|
|
from types import SimpleNamespace
|
|
from typing import TYPE_CHECKING
|
|
|
|
import httpx
|
|
from fastapi import Response
|
|
|
|
from signal_bot import app as app_module
|
|
from signal_bot.app import healthz
|
|
from signal_bot.liveness import Liveness
|
|
from signal_bot.versioning import POPE, __version__, latin_name, pope_line
|
|
|
|
if TYPE_CHECKING:
|
|
from signal_bot.config import Config
|
|
|
|
DEAD_BODY = {"error": "Failed to get linked devices: Closed unexpectedly"}
|
|
|
|
|
|
def _gate(config: Config, response: httpx.Response) -> Liveness:
|
|
"""A liveness gate whose probe always gets this answer."""
|
|
client = httpx.AsyncClient(transport=httpx.MockTransport(lambda request: response))
|
|
return Liveness(config, client)
|
|
|
|
|
|
def _install(liveness: Liveness) -> None:
|
|
"""Point the app at a connected starter carrying this gate."""
|
|
runtime = SimpleNamespace(bot=SimpleNamespace(liveness=liveness))
|
|
app_module.app.state.starter = SimpleNamespace(runtime=runtime, reason="")
|
|
|
|
|
|
async def test_a_bot_that_has_not_connected_yet_says_connecting() -> None:
|
|
"""Booting is not deafness, and must not be reported as it."""
|
|
app_module.app.state.starter = SimpleNamespace(
|
|
runtime=None, reason="connecting to http://localhost:9922, attempt 3: ConnectError"
|
|
)
|
|
response = Response()
|
|
body = await healthz(response)
|
|
assert response.status_code == httpx.codes.SERVICE_UNAVAILABLE
|
|
assert body["status"] == "connecting"
|
|
assert "attempt 3" in body["reason"]
|
|
|
|
|
|
async def test_a_healthy_bot_answers_ok(config: Config) -> None:
|
|
_install(_gate(config, httpx.Response(200, json=[])))
|
|
response = Response()
|
|
body = await healthz(response)
|
|
assert body["status"] == "ok"
|
|
assert response.status_code == httpx.codes.OK
|
|
|
|
|
|
async def test_a_deaf_bot_answers_503_and_says_why(config: Config) -> None:
|
|
"""The failure this exists for: up, listening, and hearing nothing."""
|
|
live = _gate(replace(config, silence_seconds=0), httpx.Response(400, json=DEAD_BODY))
|
|
for _ in range(3):
|
|
await live.check()
|
|
_install(live)
|
|
|
|
response = Response()
|
|
body = await healthz(response)
|
|
assert response.status_code == httpx.codes.SERVICE_UNAVAILABLE
|
|
assert body["status"] == "deaf"
|
|
assert "Closed unexpectedly" in body["reason"]
|
|
|
|
|
|
async def test_healthz_names_the_build_either_way(config: Config) -> None:
|
|
"""Without this you cannot ask a running bot which version it is."""
|
|
_install(_gate(config, httpx.Response(200, json=[])))
|
|
healthy = await healthz(Response())
|
|
assert healthy["version"] == __version__
|
|
assert healthy["pope"] == latin_name(POPE)
|
|
assert healthy["papacy"] == pope_line(POPE)
|
|
|
|
deaf_gate = _gate(replace(config, silence_seconds=0), httpx.Response(400, json=DEAD_BODY))
|
|
for _ in range(3):
|
|
await deaf_gate.check()
|
|
_install(deaf_gate)
|
|
deaf = await healthz(Response())
|
|
assert deaf["version"] == __version__
|
|
assert deaf["pope"] == latin_name(POPE)
|
|
assert deaf["papacy"] == pope_line(POPE)
|