Newer
Older
gn-synapse-client-py / tests / unit / test_envelope.py
@Eugene Sukhodolskiy Eugene Sukhodolskiy 1 day ago 3 KB Initial client library skeleton (v0.1.0)
"""Юнит-тесты envelope.py — контракт зеркалит app/api/schemas.py сервера."""

import logging
from datetime import UTC, datetime

import pytest

from gnexus_synapse.envelope import build_envelope, warn_bad_user_id
from gnexus_synapse.exceptions import SynapseValidationError


def test_minimal_envelope_exact_keys():
    env = build_envelope("bugtrail", "test", "failed")
    assert env == {"source": "bugtrail", "subject": "test", "action": "failed"}


def test_none_fields_never_sent():
    # сервер extra="forbid" — None-поле это 422; клиент не должен их слать
    env = build_envelope("s", "x", "y", payload=None, dedup_key=None, ttl_seconds=None,
                         scheduled_at=None)
    assert set(env) == {"source", "subject", "action"}
    env2 = build_envelope("s", "x", "y", payload={})
    assert "payload" not in env2


def test_full_envelope_values():
    ts = datetime(2026, 10, 3, 12, 0, tzinfo=UTC)
    env = build_envelope("s", "x", "y", priority="high", payload={"user_id": "42"},
                         dedup_key="d-1", ttl_seconds=60, scheduled_at=ts)
    assert env == {"source": "s", "subject": "x", "action": "y", "priority": "high",
                   "payload": {"user_id": "42"}, "dedup_key": "d-1", "ttl_seconds": 60,
                   "scheduled_at": "2026-10-03T12:00:00+00:00"}
    # default priority не шлётся — равен серверному дефолту
    env3 = build_envelope("s", "x", "y", priority="normal")
    assert "priority" not in env3


def test_pattern_names():
    # валидные
    for name in ("a", "bugtrail", "test.failed", "task-created", "v2", "a1_b.c-d"):
        build_envelope(name, name, name)
    # невалидные: uppercase, пробел, ведущий/хвостовой разделитель, пусто, длина 65
    for bad in ("Bugtrail", "a b", "-lead", "trail-", "_lead", "trail_", "", "a" * 65, "спецсимволы"):
        with pytest.raises(SynapseValidationError):
            build_envelope(bad, "x", "y")
        with pytest.raises(SynapseValidationError):
            build_envelope("s", bad, "y")
        with pytest.raises(SynapseValidationError):
            build_envelope("s", "x", bad)


def test_bad_priority_ttl_dedup():
    with pytest.raises(SynapseValidationError, match="priority"):
        build_envelope("s", "x", "y", priority="urgent")
    for bad_ttl in (0, -5, 604801, 1.5, "3600", True):
        with pytest.raises(SynapseValidationError, match="ttl_seconds"):
            build_envelope("s", "x", "y", ttl_seconds=bad_ttl)
    assert build_envelope("s", "x", "y", ttl_seconds=604800)["ttl_seconds"] == 604800
    with pytest.raises(SynapseValidationError, match="dedup_key"):
        build_envelope("s", "x", "y", dedup_key="d" * 256)
    assert "dedup_key" not in build_envelope("s", "x", "y", dedup_key="")
    with pytest.raises(SynapseValidationError, match="payload"):
        build_envelope("s", "x", "y", payload=["не", "словарь"])  # type: ignore[arg-type]


def test_naive_scheduled_at_treated_as_utc():
    env = build_envelope("s", "x", "y", scheduled_at=datetime(2026, 10, 3, 12, 0))
    assert env["scheduled_at"] == "2026-10-03T12:00:00+00:00"


def test_user_id_warning(caplog):
    log = logging.getLogger("test.syn")
    ok = {"user_id": "42", "x": 1}
    warn_bad_user_id(ok, log)
    warn_bad_user_id({"user_id": ""}, log)
    warn_bad_user_id({"user_id": None}, log)
    warn_bad_user_id({"user_id": {}} , log)
    warn_bad_user_id({"user_id": True}, log)
    warn_bad_user_id({}, log)
    # 5 warning'ов (пустая строка, None, dict, bool, отсутствие)
    assert len([r for r in caplog.records if r.levelno == logging.WARNING]) == 5
    warn_bad_user_id({"user_id": 42}, log)  # число ок