"""verify_webhook: подпись, freshness, парс конверта."""
from __future__ import annotations
import json
from gnexus_synapse import (
DEFAULT_MAX_SKEW,
SIGNATURE_HEADER,
SynapseWebhookError,
make_signature,
verify_webhook,
)
SECRET = "s2s-secret-test"
BODY = json.dumps(
{
"event_id": "11111111-1111-1111-1111-111111111111",
"source": "monitoring",
"subject": "container",
"action": "down",
"priority": "critical",
"payload": {"container": "api"},
}
).encode()
def test_happy_path_returns_envelope() -> None:
header = make_signature(BODY, SECRET, timestamp=1_000_000)
envelope = verify_webhook(
BODY, {SIGNATURE_HEADER: header}, SECRET, now=1_000_000
)
assert envelope["source"] == "monitoring"
assert envelope["event_id"] == "11111111-1111-1111-1111-111111111111"
def test_header_search_is_case_insensitive() -> None:
header = make_signature(BODY, SECRET, timestamp=1_000_000)
envelope = verify_webhook(
BODY,
{"X-GNEXUS-SIGNATURE": header, "X-Synapse-Source": "monitoring"},
SECRET,
now=1_000_000,
)
assert envelope["action"] == "down"
def test_wrong_secret_rejected() -> None:
header = make_signature(BODY, "other-secret", timestamp=1_000_000)
try:
verify_webhook(BODY, {SIGNATURE_HEADER: header}, SECRET, now=1_000_000)
except SynapseWebhookError as ex:
assert "не совпала" in ex.detail
else:
raise AssertionError("чужой секрет принят")
def test_replay_rejected() -> None:
header = make_signature(BODY, SECRET, timestamp=1_000_000)
try:
verify_webhook(
BODY, {SIGNATURE_HEADER: header}, SECRET, now=1_000_000 + DEFAULT_MAX_SKEW + 1
)
except SynapseWebhookError as ex:
assert "replay" in ex.detail or "свежее" in ex.detail
else:
raise AssertionError("старая доставка принята")
def test_modified_body_rejected() -> None:
header = make_signature(BODY, SECRET, timestamp=1_000_000)
try:
verify_webhook(BODY + b" ", {SIGNATURE_HEADER: header}, SECRET, now=1_000_000)
except SynapseWebhookError:
pass
else:
raise AssertionError("искажённое тело принято")
def test_missing_and_broken_headers_rejected() -> None:
for headers in ({}, {SIGNATURE_HEADER: "t=1"}, {SIGNATURE_HEADER: "garbage"},
{SIGNATURE_HEADER: f"t={1_000_000},v1="}):
try:
verify_webhook(BODY, dict(headers), SECRET, now=1_000_000)
except SynapseWebhookError:
pass
else:
raise AssertionError(f'заголовок {headers} принят без подписи')
def test_non_json_body_rejected() -> None:
header = make_signature(b"not json", SECRET, timestamp=1_000_000)
try:
verify_webhook(b"not json", {SIGNATURE_HEADER: header}, SECRET, now=1_000_000)
except SynapseWebhookError:
pass
else:
raise AssertionError("не-JSON тело принято")
def test_list_body_rejected() -> None:
header = make_signature(b"[1,2]", SECRET, timestamp=1_000_000)
try:
verify_webhook(b"[1,2]", {SIGNATURE_HEADER: header}, SECRET, now=1_000_000)
except SynapseWebhookError:
pass
else:
raise AssertionError("JSON-массив принят как конверт")
def test_parse_event_type() -> None:
from gnexus_synapse import parse_event_type
assert parse_event_type("monitoring.container.down") == ("monitoring", "container", "down")
assert parse_event_type("a.b") is None
assert parse_event_type("..") is None
assert parse_event_type(None) is None
def test_ack() -> None:
from gnexus_synapse import ack
assert ack() == {"received": True}
assert ack("11111111-1111-1111-1111-111111111111") == {
"received": True, "event_id": "11111111-1111-1111-1111-111111111111",
}