import hashlib import hmac import time import pytest from gaiia import WebhookSignatureError, verify_webhook_signature def _make_header(secret: str, timestamp: int, body: bytes) -> str: sig = hmac.new( secret.encode(), f"{timestamp}.{body.decode()}".encode(), hashlib.sha256 ).hexdigest() return f"t={timestamp},v1={sig}" def test_valid_signature_passes(): secret = "whsec_test" body = b'{"hello":"world"}' ts = int(time.time()) header = _make_header(secret, ts, body) verify_webhook_signature(header_value=header, raw_body=body, secret=secret) def test_tampered_body_fails(): secret = "whsec_test" body = b'{"hello":"world"}' ts = int(time.time()) header = _make_header(secret, ts, body) with pytest.raises(WebhookSignatureError): verify_webhook_signature( header_value=header, raw_body=b'{"hello":"WORLD"}', secret=secret ) def test_old_timestamp_rejected(): secret = "whsec_test" body = b'{"x":1}' ts = int(time.time()) - 3600 header = _make_header(secret, ts, body) with pytest.raises(WebhookSignatureError): verify_webhook_signature( header_value=header, raw_body=body, secret=secret, tolerance_seconds=60 ) def test_multiple_signatures_one_match(): secret = "whsec_test" body = b"abc" ts = int(time.time()) real = _make_header(secret, ts, body).split("v1=")[1] header = f"t={ts},v1=ffffffff,v1={real}" verify_webhook_signature(header_value=header, raw_body=body, secret=secret) def test_missing_components(): with pytest.raises(WebhookSignatureError): verify_webhook_signature(header_value="", raw_body=b"", secret="x") with pytest.raises(WebhookSignatureError): verify_webhook_signature(header_value="v1=abc", raw_body=b"", secret="x") with pytest.raises(WebhookSignatureError): verify_webhook_signature(header_value="t=123", raw_body=b"", secret="x")