afterhours-shift-manager/tests/release_notifier/test_handler.py
Adam Moussa 1ccedff872 Add changelog-driven releases and App Home tab
Version the bot continuously from CHANGELOG.md (the single source of
truth for both the version and the staff-readable notes) and surface
changes to users in two ways:

- A new afterhours-release-notifier Lambda posts a "What's New" message
  to the shift channel on minor/major releases (patches stay silent).
- The bot gains an App Home "About" tab showing what it does, the
  command list, and the current version's notes.

release.yaml runs on Deploy success (not release:published — GITHUB_TOKEN
events don't start downstream workflows), checks out the deployed commit,
and tags + publishes a GitHub Release + invokes the notifier. It assumes a
dedicated, boundary-carrying OIDC role scoped to InvokeFunction on the
notifier; the account's cfn role gates role creation on that boundary.
The manual Version Bump workflow is retired. A CI guard enforces that a
CHANGELOG edit is a clean SemVer bump and that the in-package copy matches.
2026-06-11 19:15:20 -04:00

67 lines
2.1 KiB
Python

"""Tests for the release-notifier Lambda handler."""
from unittest.mock import MagicMock
import pytest
@pytest.fixture
def slack(notifier_app, monkeypatch):
"""Fake Slack WebClient; chat_postMessage returns a message ts."""
fake = MagicMock(name="slack")
fake.chat_postMessage.return_value = {"ts": "111.222"}
monkeypatch.setattr(notifier_app, "WebClient", MagicMock(return_value=fake))
monkeypatch.setattr(notifier_app, "get_secret", lambda _id: "xoxb-test")
return fake
@pytest.fixture
def env(monkeypatch):
monkeypatch.setenv(
"SLACK_BOT_TOKEN_SECRET", "afterhours-shift-manager/slack-bot-token"
)
monkeypatch.setenv("SHIFT_CHANNEL", "C_RELEASES")
def test_posts_announcement_to_channel(notifier_app, slack, env):
result = notifier_app.handler(
{
"version": "1.10.0",
"notes": "**Release notes** now self-announce.",
"date_label": "June 11, 2026",
},
None,
)
assert result == {"announced": True, "version": "1.10.0", "ts": "111.222"}
slack.chat_postMessage.assert_called_once()
kwargs = slack.chat_postMessage.call_args.kwargs
assert kwargs["channel"] == "C_RELEASES"
assert kwargs["text"] == "What's New — v1.10.0"
# Markdown was converted to Slack mrkdwn in the rendered blocks.
rendered = str(kwargs["blocks"])
assert "*Release notes*" in rendered
def test_uses_the_slack_bot_token_secret(notifier_app, slack, env, monkeypatch):
seen = {}
monkeypatch.setattr(
notifier_app, "get_secret", lambda sid: seen.setdefault("id", sid) or "xoxb"
)
notifier_app.handler({"version": "2.0.0", "notes": "Big."}, None)
assert seen["id"] == "afterhours-shift-manager/slack-bot-token"
@pytest.mark.parametrize(
"event",
[
{},
{"version": "1.10.0"}, # missing notes
{"notes": "x"}, # missing version
{"version": "", "notes": "x"}, # empty version
],
)
def test_rejects_incomplete_event(notifier_app, slack, env, event):
with pytest.raises(ValueError):
notifier_app.handler(event, None)
slack.chat_postMessage.assert_not_called()