Add NotificationClient methods for config and subscription management with tests

This commit is contained in:
claudi 2026-04-07 09:22:39 +02:00
parent 8f31f433d2
commit 1a9f924764
3 changed files with 243 additions and 0 deletions

View file

@ -69,6 +69,8 @@ class ApiTransport:
response = self.request(method, path, **kwargs)
if response.status_code == 204:
return None
if not response.content or not response.content.strip():
return None
try:
return response.json()
except ValueError as exc:

View file

@ -2,10 +2,16 @@ from __future__ import annotations
from ebay_client.core.http.transport import ApiTransport
from ebay_client.generated.notification.models import (
Config,
CreateSubscriptionFilterRequest,
CreateSubscriptionRequest,
Destination,
DestinationRequest,
DestinationSearchResponse,
PublicKey,
Subscription,
SubscriptionFilter,
UpdateSubscriptionRequest,
SubscriptionSearchResponse,
Topic,
TopicSearchResponse,
@ -20,6 +26,23 @@ class NotificationClient:
def __init__(self, transport: ApiTransport) -> None:
self.transport = transport
def get_config(self) -> Config:
return self.transport.request_model(
Config,
"GET",
"/commerce/notification/v1/config",
scopes=[NOTIFICATION_SCOPE],
)
def update_config(self, payload: Config) -> None:
self.transport.request_json(
"PUT",
"/commerce/notification/v1/config",
scopes=[NOTIFICATION_SCOPE],
headers={"Content-Type": "application/json"},
json_body=payload.model_dump(by_alias=True, exclude_none=True),
)
def get_topics(self, *, limit: int | None = None, continuation_token: str | None = None) -> TopicSearchResponse:
return self.transport.request_model(
TopicSearchResponse,
@ -55,6 +78,30 @@ class NotificationClient:
json_body=payload.model_dump(by_alias=True, exclude_none=True),
)
def get_destination(self, destination_id: str) -> Destination:
return self.transport.request_model(
Destination,
"GET",
f"/commerce/notification/v1/destination/{destination_id}",
scopes=[NOTIFICATION_SCOPE],
)
def update_destination(self, destination_id: str, payload: DestinationRequest) -> None:
self.transport.request_json(
"PUT",
f"/commerce/notification/v1/destination/{destination_id}",
scopes=[NOTIFICATION_SCOPE],
headers={"Content-Type": "application/json"},
json_body=payload.model_dump(by_alias=True, exclude_none=True),
)
def delete_destination(self, destination_id: str) -> None:
self.transport.request_json(
"DELETE",
f"/commerce/notification/v1/destination/{destination_id}",
scopes=[NOTIFICATION_SCOPE],
)
def get_subscriptions(self, *, limit: int | None = None, continuation_token: str | None = None) -> SubscriptionSearchResponse:
return self.transport.request_model(
SubscriptionSearchResponse,
@ -73,6 +120,72 @@ class NotificationClient:
json_body=payload.model_dump(by_alias=True, exclude_none=True),
)
def get_subscription(self, subscription_id: str) -> Subscription:
return self.transport.request_model(
Subscription,
"GET",
f"/commerce/notification/v1/subscription/{subscription_id}",
scopes=[NOTIFICATION_SCOPE, NOTIFICATION_SUBSCRIPTION_READ_SCOPE],
)
def update_subscription(self, subscription_id: str, payload: UpdateSubscriptionRequest) -> None:
self.transport.request_json(
"PUT",
f"/commerce/notification/v1/subscription/{subscription_id}",
scopes=[NOTIFICATION_SCOPE, NOTIFICATION_SUBSCRIPTION_SCOPE],
headers={"Content-Type": "application/json"},
json_body=payload.model_dump(by_alias=True, exclude_none=True),
)
def delete_subscription(self, subscription_id: str) -> None:
self.transport.request_json(
"DELETE",
f"/commerce/notification/v1/subscription/{subscription_id}",
scopes=[NOTIFICATION_SCOPE, NOTIFICATION_SUBSCRIPTION_SCOPE],
)
def create_subscription_filter(
self,
subscription_id: str,
payload: CreateSubscriptionFilterRequest,
) -> dict[str, object] | None:
return self.transport.request_json(
"POST",
f"/commerce/notification/v1/subscription/{subscription_id}/filter",
scopes=[NOTIFICATION_SCOPE, NOTIFICATION_SUBSCRIPTION_SCOPE],
headers={"Content-Type": "application/json"},
json_body=payload.model_dump(by_alias=True, exclude_none=True),
)
def get_subscription_filter(self, subscription_id: str, filter_id: str) -> SubscriptionFilter:
return self.transport.request_model(
SubscriptionFilter,
"GET",
f"/commerce/notification/v1/subscription/{subscription_id}/filter/{filter_id}",
scopes=[NOTIFICATION_SCOPE, NOTIFICATION_SUBSCRIPTION_READ_SCOPE],
)
def delete_subscription_filter(self, subscription_id: str, filter_id: str) -> None:
self.transport.request_json(
"DELETE",
f"/commerce/notification/v1/subscription/{subscription_id}/filter/{filter_id}",
scopes=[NOTIFICATION_SCOPE, NOTIFICATION_SUBSCRIPTION_SCOPE],
)
def disable_subscription(self, subscription_id: str) -> None:
self.transport.request_json(
"POST",
f"/commerce/notification/v1/subscription/{subscription_id}/disable",
scopes=[NOTIFICATION_SCOPE, NOTIFICATION_SUBSCRIPTION_SCOPE],
)
def enable_subscription(self, subscription_id: str) -> None:
self.transport.request_json(
"POST",
f"/commerce/notification/v1/subscription/{subscription_id}/enable",
scopes=[NOTIFICATION_SCOPE, NOTIFICATION_SUBSCRIPTION_SCOPE],
)
def test_subscription(self, subscription_id: str) -> None:
self.transport.request_json(
"POST",

View file

@ -14,9 +14,16 @@ from ebay_client.generated.feed.models import TaskCollection
from ebay_client.generated.fulfillment.models import Order
from ebay_client.generated.inventory.models import InventoryItemWithSkuLocaleGroupid
from ebay_client.generated.notification.models import (
Config,
CreateSubscriptionFilterRequest,
CreateSubscriptionRequest,
DeliveryConfig,
Subscription,
SubscriptionFilter,
SubscriptionPayloadDetail,
DestinationRequest,
TopicSearchResponse,
UpdateSubscriptionRequest,
)
from ebay_client.inventory.client import InventoryClient
from ebay_client.notification.client import NotificationClient
@ -74,6 +81,127 @@ def test_notification_wrapper_serializes_typed_request_model(httpx_mock: HTTPXMo
assert body["deliveryConfig"]["endpoint"] == "https://example.test/webhook"
def test_notification_config_wrapper_round_trip(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
method="GET",
url="https://api.ebay.com/commerce/notification/v1/config",
json={"alertEmail": "alerts@example.test"},
)
httpx_mock.add_response(
method="PUT",
url="https://api.ebay.com/commerce/notification/v1/config",
status_code=204,
)
client = NotificationClient(build_transport())
config = client.get_config()
client.update_config(Config(alertEmail="ops@example.test"))
assert isinstance(config, Config)
assert config.alertEmail == "alerts@example.test"
request = httpx_mock.get_requests()[1]
body = json.loads(request.content.decode("utf-8"))
assert body["alertEmail"] == "ops@example.test"
def test_notification_subscription_wrapper_serializes_and_returns_models(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
method="POST",
url="https://api.ebay.com/commerce/notification/v1/subscription",
json={},
status_code=201,
)
httpx_mock.add_response(
method="GET",
url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1",
json={"subscriptionId": "SUB-1", "topicId": "TOPIC-1", "status": "ENABLED"},
)
httpx_mock.add_response(
method="PUT",
url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1",
status_code=204,
)
httpx_mock.add_response(
method="POST",
url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1/enable",
status_code=204,
)
httpx_mock.add_response(
method="POST",
url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1/test",
status_code=202,
)
client = NotificationClient(build_transport())
create_payload = CreateSubscriptionRequest(
destinationId="DEST-1",
topicId="TOPIC-1",
status="DISABLED",
payload=SubscriptionPayloadDetail(
deliveryProtocol="HTTPS",
format="JSON",
schemaVersion="1.0",
),
)
update_payload = UpdateSubscriptionRequest(status="ENABLED", destinationId="DEST-1")
client.create_subscription(create_payload)
subscription = client.get_subscription("SUB-1")
client.update_subscription("SUB-1", update_payload)
client.enable_subscription("SUB-1")
client.test_subscription("SUB-1")
assert isinstance(subscription, Subscription)
assert subscription.subscriptionId == "SUB-1"
create_request = httpx_mock.get_requests()[0]
create_body = json.loads(create_request.content.decode("utf-8"))
assert create_body["destinationId"] == "DEST-1"
assert create_body["payload"]["deliveryProtocol"] == "HTTPS"
update_request = httpx_mock.get_requests()[2]
update_body = json.loads(update_request.content.decode("utf-8"))
assert update_body == {"destinationId": "DEST-1", "status": "ENABLED"}
def test_notification_subscription_filter_wrapper_serializes_and_returns_model(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
method="POST",
url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1/filter",
json={},
status_code=201,
)
httpx_mock.add_response(
method="GET",
url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1/filter/FILTER-1",
json={"filterId": "FILTER-1", "subscriptionId": "SUB-1", "filterStatus": "ENABLED"},
)
httpx_mock.add_response(
method="DELETE",
url="https://api.ebay.com/commerce/notification/v1/subscription/SUB-1/filter/FILTER-1",
status_code=204,
)
client = NotificationClient(build_transport())
payload = CreateSubscriptionFilterRequest(
filterSchema={
"properties": {
"data": {
"type": "object",
}
}
}
)
client.create_subscription_filter("SUB-1", payload)
subscription_filter = client.get_subscription_filter("SUB-1", "FILTER-1")
client.delete_subscription_filter("SUB-1", "FILTER-1")
assert isinstance(subscription_filter, SubscriptionFilter)
assert subscription_filter.filterId == "FILTER-1"
request = httpx_mock.get_requests()[0]
body = json.loads(request.content.decode("utf-8"))
assert body["filterSchema"]["properties"]["data"]["type"] == "object"
def test_inventory_wrapper_returns_inventory_item_model(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
method="GET",