Files
notify-bridge/packages/server/tests/test_release_service.py
T
alexei.dolgolyov ba199f24bd feat: deferred dispatch, release-check provider, settings polish
- Defer quiet-hours dispatches into new deferred_dispatch table; drain
  job + periodic catch-up scan re-fire at window end with coalescing on
  (link, event_type, collection_id).
- Add ON DELETE SET NULL migration on event_log_id and partial unique
  index on (link_id, collection_id, event_type) WHERE status='pending'.
- Add release-check provider abstraction (Gitea/GitHub) with SSRF-safe
  URL validation, settings UI cassette, and scheduled polling.
- Replace importlib-only version lookup with version.py helper that
  prefers the higher of installed metadata vs source pyproject so stale
  editable dev installs stop misreporting.
- Aurora frontend polish: MetaStrip component, ReleaseCassette,
  EventDetailModal expansion, and i18n additions.
2026-05-12 02:58:07 +03:00

145 lines
5.0 KiB
Python

"""Tests for the release_check service (interval clamping + status endpoints + persistence)."""
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
def test_parse_interval_hours_clamps_and_defaults() -> None:
from notify_bridge_server.services.release_check import parse_interval_hours
assert parse_interval_hours("12") == 12
assert parse_interval_hours("") == 12 # default
assert parse_interval_hours(None) == 12
assert parse_interval_hours("0") == 1 # clamped to min
assert parse_interval_hours("9999") == 168 # clamped to max
assert parse_interval_hours("not-a-number") == 12 # fallback to default
assert parse_interval_hours("24") == 24
def test_release_endpoint_anonymous_is_rejected(tmp_data_dir) -> None: # noqa: ARG001
"""GET /api/settings/release requires auth — same as other settings."""
from notify_bridge_server.main import app
with TestClient(app) as client:
resp = client.get("/api/settings/release")
# Either 401 (missing token) or 403 (not authenticated) is acceptable.
assert resp.status_code in (401, 403)
def test_release_force_check_requires_admin(tmp_data_dir) -> None: # noqa: ARG001
from notify_bridge_server.main import app
with TestClient(app) as client:
resp = client.post("/api/settings/release/check")
assert resp.status_code in (401, 403)
def test_release_test_requires_admin(tmp_data_dir) -> None: # noqa: ARG001
from notify_bridge_server.main import app
with TestClient(app) as client:
resp = client.post(
"/api/settings/release/test",
json={"provider_kind": "gitea", "provider_url": "https://x.example.com", "provider_repo": "a/b"},
)
assert resp.status_code in (401, 403)
# --- Persistence round-trip -------------------------------------------------
@pytest.mark.asyncio
async def test_persist_release_state_round_trip(tmp_data_dir, monkeypatch) -> None: # noqa: ARG001
"""Write a fake ReleaseInfo, read it back via load_status, assert flags."""
from notify_bridge_core.release import ReleaseInfo
from notify_bridge_server.database.engine import init_db
from notify_bridge_server.services.release_check import (
load_status,
persist_release_state,
)
await init_db()
info = ReleaseInfo(
tag="v0.9.0",
version="0.9.0",
name="0.9.0 — Aurora",
body="Release notes",
url="https://example.com/x/y/releases/tag/v0.9.0",
published_at="2026-06-01T00:00:00Z",
prerelease=False,
draft=False,
)
await persist_release_state(
checked_at="2026-06-01T00:01:00+00:00",
error=None,
info=info,
)
# Force the comparator to see an older "current" so update_available
# comes out True regardless of the actual installed package version.
monkeypatch.setattr(
"notify_bridge_server.services.release_check._server_version",
lambda: "0.7.0",
)
status = await load_status()
assert status.latest == "0.9.0"
assert status.latest_tag == "v0.9.0"
assert status.update_available is True
assert status.error is None
assert status.latest_body == "Release notes"
@pytest.mark.asyncio
async def test_persist_release_state_clears_on_none_info(tmp_data_dir, monkeypatch) -> None: # noqa: ARG001
"""A persist call with ``info=None`` must blank all the latest-* fields."""
from notify_bridge_core.release import ReleaseInfo
from notify_bridge_server.database.engine import init_db
from notify_bridge_server.services.release_check import (
load_status,
persist_release_state,
)
await init_db()
# Seed a populated row.
await persist_release_state(
checked_at="2026-06-01T00:00:00+00:00",
error=None,
info=ReleaseInfo(tag="v9.9.9", version="9.9.9"),
)
# Now wipe by passing info=None — mimics the "provider_changed" flow.
await persist_release_state(
checked_at="2026-06-01T00:02:00+00:00",
error="provider_changed",
info=None,
)
monkeypatch.setattr(
"notify_bridge_server.services.release_check._server_version",
lambda: "0.7.0",
)
status = await load_status()
assert status.latest is None
assert status.latest_tag is None
assert status.update_available is False
assert status.error == "provider_changed"
# --- Version resolver -------------------------------------------------------
def test_resolve_version_prefers_source_pyproject() -> None:
"""When pyproject.toml is alongside the source, prefer the higher of (installed, source)."""
from notify_bridge_server.version import resolve_version
v = resolve_version()
assert v != "0.0.0+unknown"
# If the editable install is stale (e.g. 0.3.2) but pyproject says 0.7.2,
# resolve_version must return 0.7.2 (or higher) — the resolver's
# whole purpose. We test the "not stale" half of the contract here.
parts = v.split(".")
assert len(parts) >= 2
assert parts[0].isdigit()