Add webhook utilities and FastAPI integration for eBay notifications; enhance tests for request handling

This commit is contained in:
claudi 2026-04-07 09:49:06 +02:00
parent 10008a0edc
commit 30b62dedab
5 changed files with 293 additions and 2 deletions

View file

@ -8,7 +8,11 @@ from cryptography.hazmat.primitives.asymmetric import ec
from ebay_client.generated.notification.models import PublicKey
from ebay_client.notification.webhook import WebhookChallengeHandler, WebhookSignatureParser
from ebay_client.notification.webhook import WebhookPublicKeyResolver, WebhookSignatureValidator
from ebay_client.notification.webhook import (
WebhookPublicKeyResolver,
WebhookRequestHandler,
WebhookSignatureValidator,
)
def test_challenge_handler_builds_sha256_response() -> None:
@ -82,4 +86,105 @@ def test_signature_validator_verifies_base64_json_header_and_der_key() -> None:
)
validator = WebhookSignatureValidator(resolver)
assert validator.validate(header_value=header, body=body) is True
assert validator.validate(header_value=header, body=body) is True
def test_request_handler_returns_json_challenge_response() -> None:
handler = WebhookRequestHandler(
signature_validator=WebhookSignatureValidator(
WebhookPublicKeyResolver(lambda _: PublicKey(key="", algorithm="ECDSA", digest="SHA256"))
)
)
response = handler.handle_challenge(
challenge_code="challenge",
verification_token="verification",
endpoint="https://example.test/webhook",
)
assert response.status_code == 200
assert response.headers["Content-Type"] == "application/json"
body = json.loads(response.body.decode("utf-8"))
assert "challengeResponse" in body
def test_request_handler_accepts_verified_notification_and_parses_event() -> None:
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
public_key_der = public_key.public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
body = json.dumps(
{
"notificationId": "abc-123",
"publishDate": "2026-04-07T00:00:00.000Z",
"topicId": "MARKETPLACE_ACCOUNT_DELETION",
"data": {"userId": "user-1"},
"schemaVersion": "1.0",
}
).encode("utf-8")
signature = private_key.sign(body, ec.ECDSA(hashes.SHA256()))
header = base64.b64encode(
json.dumps(
{
"alg": "ECDSA",
"kid": "public-key-1",
"signature": base64.b64encode(signature).decode("ascii"),
"digest": "SHA256",
}
).encode("utf-8")
).decode("ascii")
resolver = WebhookPublicKeyResolver(
lambda key_id: PublicKey(
key=base64.b64encode(public_key_der).decode("ascii"),
algorithm="ECDSA",
digest="SHA256",
)
)
handler = WebhookRequestHandler(signature_validator=WebhookSignatureValidator(resolver))
result = handler.handle_notification(signature_header=header, body=body)
assert result.response.status_code == 200
assert result.event is not None
assert result.event.notification_id == "abc-123"
assert result.event.topic_id == "MARKETPLACE_ACCOUNT_DELETION"
assert result.event.metadata["schemaVersion"] == "1.0"
def test_request_handler_returns_412_for_invalid_signature() -> None:
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
public_key_der = public_key.public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
body = b'{"notificationId":"abc-123"}'
wrong_body = b'{"notificationId":"def-456"}'
signature = private_key.sign(body, ec.ECDSA(hashes.SHA256()))
header = base64.b64encode(
json.dumps(
{
"alg": "ECDSA",
"kid": "public-key-1",
"signature": base64.b64encode(signature).decode("ascii"),
"digest": "SHA256",
}
).encode("utf-8")
).decode("ascii")
resolver = WebhookPublicKeyResolver(
lambda key_id: PublicKey(
key=base64.b64encode(public_key_der).decode("ascii"),
algorithm="ECDSA",
digest="SHA256",
)
)
handler = WebhookRequestHandler(signature_validator=WebhookSignatureValidator(resolver))
result = handler.handle_notification(signature_header=header, body=wrong_body)
assert result.response.status_code == 412
assert result.event is None