feat(s52): webhook notifier with fail-closed opt-in gate + injectable transport
This commit is contained in:
parent
f206dfbf65
commit
61c4cb2d26
3 changed files with 119 additions and 1 deletions
|
|
@ -6,13 +6,31 @@ import MAF-tainted modules freely (``verdicts``) — only ``notify.py`` itself m
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import io
|
||||
import json
|
||||
|
||||
from portfolio_optimiser.notify import ConsoleNotifier, FileNotifier, Notifier, _verdict_payload
|
||||
from portfolio_optimiser.notify import (
|
||||
ConsoleNotifier,
|
||||
FileNotifier,
|
||||
Notifier,
|
||||
WebhookNotifier,
|
||||
_urllib_post,
|
||||
_verdict_payload,
|
||||
)
|
||||
from portfolio_optimiser.verdicts import ProposalFeatures, Verdict, capture_verdict, verdict_to_dict
|
||||
|
||||
|
||||
def _make_post(recorder: list[tuple[str, str]]):
|
||||
"""A canned webhook transport: records ``(url, body)``, opens no socket. Local to this file
|
||||
(mirrors ``test_ingest_http._make_get`` locality; never conftest)."""
|
||||
|
||||
def post(url: str, body: str) -> None:
|
||||
recorder.append((url, body))
|
||||
|
||||
return post
|
||||
|
||||
|
||||
def _verdict() -> Verdict:
|
||||
return capture_verdict(
|
||||
ProposalFeatures(
|
||||
|
|
@ -69,3 +87,21 @@ def test_file_notifier_byte_deterministic(tmp_path) -> None:
|
|||
assert payload["id"] == verdict.id
|
||||
assert payload["proposal_features"]["affected_codes"] == ["03.1", "05.2"]
|
||||
assert not any(k in payload for k in ("date", "timestamp", "created", "ts"))
|
||||
|
||||
|
||||
def test_webhook_posts_correct_payload() -> None:
|
||||
"""The injected transport receives the exact ``(url, nested verdict_to_dict payload)``."""
|
||||
calls: list[tuple[str, str]] = []
|
||||
verdict = _verdict()
|
||||
url = "https://hooks.example.test/T000/B000/token"
|
||||
WebhookNotifier(url, allow_egress=True, post=_make_post(calls))(verdict)
|
||||
assert len(calls) == 1
|
||||
posted_url, body = calls[0]
|
||||
assert (posted_url, json.loads(body)) == (url, verdict_to_dict(verdict))
|
||||
|
||||
|
||||
def test_webhook_default_post_is_urllib_post() -> None:
|
||||
"""inspect.signature proves the stdlib default WITHOUT opening a socket — ``_urllib_post`` is
|
||||
the single socket path and is never called by the offline suite."""
|
||||
sig = inspect.signature(WebhookNotifier.__init__)
|
||||
assert sig.parameters["post"].default is _urllib_post
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue