"""Unit tests for the Gitea webhook parser. Pure-function tests against ``parse_webhook`` using realistic Gitea payloads (trimmed to the fields the parser actually consumes). No DB or HTTP fixtures needed. """ from __future__ import annotations from notify_bridge_core.models.events import EventType from notify_bridge_core.providers.base import ServiceProviderType from notify_bridge_core.providers.gitea.event_parser import parse_webhook def _repo() -> dict: return { "id": 42, "name": "demo", "full_name": "alexei/demo", "html_url": "https://git.example.com/alexei/demo", "description": "Demo repo", "private": False, "owner": { "id": 1, "login": "alexei", "full_name": "Alexei", "email": "alexei@example.com", "avatar_url": "https://git.example.com/avatars/1", }, } def _sender() -> dict: return { "id": 1, "login": "alexei", "full_name": "Alexei", "avatar_url": "https://git.example.com/avatars/1", } def test_push_event() -> None: payload = { "ref": "refs/heads/master", "before": "0000000000000000000000000000000000000000", "after": "abcdef0123456789abcdef0123456789abcdef01", "compare_url": "https://git.example.com/alexei/demo/compare/000...abc", "commits": [ { "id": "abcdef0123456789abcdef0123456789abcdef01", "message": "feat: initial commit\n\nMore detail.", "url": "https://git.example.com/alexei/demo/commit/abcdef0", "author": { "name": "Alexei", "email": "alexei@example.com", "username": "alexei", }, "timestamp": "2026-05-16T10:00:00Z", }, { "id": "1234567890123456789012345678901234567890", "message": "chore: tweak", "url": "https://git.example.com/alexei/demo/commit/1234567", "author": {"name": "Alexei", "email": "alexei@example.com"}, "timestamp": "2026-05-16T10:05:00Z", }, ], "repository": _repo(), "sender": _sender(), } evt = parse_webhook("push", payload, provider_name="gitea-prod") assert evt is not None assert evt.event_type is EventType.PUSH assert evt.provider_type is ServiceProviderType.GITEA assert evt.collection_id == "alexei/demo" assert evt.collection_name == "alexei/demo" assert evt.extra["ref"] == "refs/heads/master" assert evt.extra["branch"] == "master" assert evt.extra["commit_count"] == 2 assert evt.extra["commits"][0]["short_id"] == "abcdef0" # The first commit's multi-line body must be preserved (.strip handles # trailing newlines but should keep the inner '\n'). assert "feat: initial commit" in evt.extra["commits"][0]["message"] def test_issue_opened() -> None: payload = { "action": "opened", "issue": { "id": 100, "number": 7, "title": "Bug: thing broken", "html_url": "https://git.example.com/alexei/demo/issues/7", "state": "open", "body": "Steps to reproduce...", "labels": [{"name": "bug"}, {"name": "p1"}], }, "repository": _repo(), "sender": _sender(), } evt = parse_webhook("issues", payload, provider_name="gitea-prod") assert evt is not None assert evt.event_type is EventType.ISSUE_OPENED assert evt.collection_id == "alexei/demo" assert evt.extra["issue_number"] == 7 assert evt.extra["issue_title"] == "Bug: thing broken" assert evt.extra["issue_labels"] == ["bug", "p1"] def test_issue_closed() -> None: payload = { "action": "closed", "issue": { "id": 100, "number": 7, "title": "Bug: thing broken", "html_url": "https://git.example.com/alexei/demo/issues/7", "state": "closed", "body": "", "labels": [], }, "repository": _repo(), "sender": _sender(), } evt = parse_webhook("issues", payload, provider_name="gitea-prod") assert evt is not None assert evt.event_type is EventType.ISSUE_CLOSED assert evt.extra["issue_state"] == "closed" def test_pr_opened() -> None: payload = { "action": "opened", "pull_request": { "id": 200, "number": 12, "title": "Add metrics endpoint", "html_url": "https://git.example.com/alexei/demo/pulls/12", "state": "open", "body": "PR body", "merged": False, "base": {"ref": "master", "label": "alexei:master"}, "head": {"ref": "feat/metrics", "label": "alexei:feat/metrics"}, "labels": [{"name": "enhancement"}], }, "repository": _repo(), "sender": _sender(), } evt = parse_webhook("pull_request", payload, provider_name="gitea-prod") assert evt is not None assert evt.event_type is EventType.PR_OPENED assert evt.extra["pr_number"] == 12 assert evt.extra["pr_merged"] is False assert evt.extra["pr_base"] == "alexei:master" assert evt.extra["pr_head"] == "alexei:feat/metrics" def test_pr_merged_resolves_from_closed_with_merged_flag() -> None: """A 'closed' action with merged=True is the merge signal — Gitea does not send a distinct event header for it, so the parser must promote PR_CLOSED -> PR_MERGED on its own.""" payload = { "action": "closed", "pull_request": { "id": 200, "number": 12, "title": "Add metrics endpoint", "html_url": "https://git.example.com/alexei/demo/pulls/12", "state": "closed", "body": "", "merged": True, "base": {"ref": "master"}, "head": {"ref": "feat/metrics"}, "labels": [], }, "repository": _repo(), "sender": _sender(), } evt = parse_webhook("pull_request", payload, provider_name="gitea-prod") assert evt is not None assert evt.event_type is EventType.PR_MERGED assert evt.extra["pr_merged"] is True def test_pr_closed_without_merge() -> None: payload = { "action": "closed", "pull_request": { "id": 200, "number": 12, "title": "Abandoned PR", "html_url": "https://git.example.com/alexei/demo/pulls/12", "state": "closed", "body": "", "merged": False, "base": {"ref": "master"}, "head": {"ref": "feat/x"}, "labels": [], }, "repository": _repo(), "sender": _sender(), } evt = parse_webhook("pull_request", payload, provider_name="gitea-prod") assert evt is not None assert evt.event_type is EventType.PR_CLOSED def test_release_published() -> None: payload = { "action": "published", "release": { "id": 9, "tag_name": "v1.2.3", "name": "Release v1.2.3", "html_url": "https://git.example.com/alexei/demo/releases/tag/v1.2.3", "body": "Bug fixes and improvements", "draft": False, "prerelease": False, }, "repository": _repo(), "sender": _sender(), } evt = parse_webhook("release", payload, provider_name="gitea-prod") assert evt is not None assert evt.event_type is EventType.RELEASE_PUBLISHED assert evt.extra["release_tag"] == "v1.2.3" assert evt.extra["release_prerelease"] is False def test_release_non_published_is_ignored() -> None: """Only ``published`` releases should produce events — drafts and edits are noise and would spam any tracker subscribed to release notifications.""" payload = { "action": "edited", "release": { "id": 9, "tag_name": "v1.2.3", "name": "x", "html_url": "", "body": "", "draft": True, "prerelease": False, }, "repository": _repo(), "sender": _sender(), } assert parse_webhook("release", payload, provider_name="g") is None def test_unknown_event_header_returns_none() -> None: payload = {"repository": _repo(), "sender": _sender()} assert parse_webhook("unknown_event", payload, provider_name="g") is None