1717 lines
No EOL
64 KiB
Python
1717 lines
No EOL
64 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from pytest_httpx import HTTPXMock
|
|
|
|
from ebay_client.account.client import AccountClient
|
|
from ebay_client.core.auth.models import OAuthToken
|
|
from ebay_client.core.http.transport import ApiTransport
|
|
from ebay_client.feed.client import CreatedFeedResource, FeedClient, FeedFileDownload
|
|
from ebay_client.fulfillment.client import CreatedShippingFulfillment, EvidenceFileDownload, FulfillmentClient
|
|
from ebay_client.generated.account.models import (
|
|
FulfillmentPolicy,
|
|
FulfillmentPolicyRequest,
|
|
PaymentPolicy,
|
|
PaymentPolicyRequest,
|
|
Programs,
|
|
ReturnPolicy,
|
|
ReturnPolicyRequest,
|
|
)
|
|
from ebay_client.generated.feed.models import TaskCollection
|
|
from ebay_client.generated.feed.models import (
|
|
CreateTaskRequest,
|
|
CreateUserScheduleRequest,
|
|
ScheduleTemplateResponse,
|
|
UpdateUserScheduleRequest,
|
|
UserScheduleResponse,
|
|
)
|
|
from ebay_client.generated.fulfillment.models import (
|
|
AcceptPaymentDisputeRequest,
|
|
AddEvidencePaymentDisputeRequest,
|
|
AddEvidencePaymentDisputeResponse,
|
|
FileEvidence,
|
|
IssueRefundRequest,
|
|
Order,
|
|
OrderLineItems,
|
|
PaymentDispute,
|
|
PaymentDisputeActivityHistory,
|
|
Refund,
|
|
ShippingFulfillmentDetails,
|
|
LineItemReference,
|
|
ContestPaymentDisputeRequest,
|
|
DisputeSummaryResponse,
|
|
UpdateEvidencePaymentDisputeRequest,
|
|
)
|
|
from ebay_client.generated.inventory.models import (
|
|
Address,
|
|
BaseResponse,
|
|
BulkEbayOfferDetailsWithKeys,
|
|
BulkGetInventoryItem,
|
|
BulkGetInventoryItemResponse,
|
|
BulkInventoryItem,
|
|
BulkInventoryItemResponse,
|
|
BulkMigrateListing,
|
|
BulkMigrateListingResponse,
|
|
BulkOffer,
|
|
BulkOfferResponse,
|
|
BulkPriceQuantity,
|
|
BulkPriceQuantityResponse,
|
|
BulkPublishResponse,
|
|
Compatibility,
|
|
EbayOfferDetailsWithId,
|
|
EbayOfferDetailsWithKeys,
|
|
FeesSummaryResponse,
|
|
GetInventoryItem,
|
|
InventoryItem,
|
|
InventoryItemGroup,
|
|
InventoryItemWithSkuLocale,
|
|
InventoryItemWithSkuLocaleGroupid,
|
|
InventoryLocation,
|
|
InventoryLocationFull,
|
|
InventoryLocationResponse,
|
|
LocationAvailabilityDetails,
|
|
LocationDetails,
|
|
LocationMapping,
|
|
LocationResponse,
|
|
MigrateListing,
|
|
NameValueList,
|
|
OfferKeyWithId,
|
|
OfferKeysWithId,
|
|
OfferResponse,
|
|
OfferPriceQuantity,
|
|
PublishByInventoryItemGroupRequest,
|
|
PublishResponse,
|
|
WithdrawByInventoryItemGroupRequest,
|
|
WithdrawResponse,
|
|
)
|
|
from ebay_client.generated.media.models import (
|
|
CreateDocumentFromUrlRequest,
|
|
CreateDocumentRequest,
|
|
CreateDocumentResponse,
|
|
CreateImageFromUrlRequest,
|
|
CreateVideoRequest,
|
|
DocumentResponse,
|
|
ImageResponse,
|
|
Video,
|
|
)
|
|
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.media.client import (
|
|
CreatedMediaResource,
|
|
DocumentWorkflowResult,
|
|
MediaClient,
|
|
VideoWorkflowResult,
|
|
extract_resource_id,
|
|
guess_media_content_type,
|
|
)
|
|
from ebay_client.notification.client import NotificationClient
|
|
|
|
|
|
class DummyOAuthClient:
|
|
def get_valid_token(
|
|
self,
|
|
*,
|
|
scopes: list[str] | None = None,
|
|
scope_options: list[list[str]] | None = None,
|
|
) -> OAuthToken:
|
|
if scopes:
|
|
resolved_scopes = scopes
|
|
elif scope_options:
|
|
resolved_scopes = scope_options[0]
|
|
else:
|
|
resolved_scopes = []
|
|
return OAuthToken(access_token="test-token", scope=" ".join(resolved_scopes))
|
|
|
|
|
|
def build_transport() -> ApiTransport:
|
|
return ApiTransport(base_url="https://api.ebay.com", oauth_client=DummyOAuthClient())
|
|
|
|
|
|
class RecordingOAuthClient:
|
|
def __init__(self) -> None:
|
|
self.last_scopes: list[str] | None = None
|
|
self.last_scope_options: list[list[str]] | None = None
|
|
|
|
def get_valid_token(
|
|
self,
|
|
*,
|
|
scopes: list[str] | None = None,
|
|
scope_options: list[list[str]] | None = None,
|
|
) -> OAuthToken:
|
|
self.last_scopes = scopes
|
|
self.last_scope_options = scope_options
|
|
resolved_scopes = scopes or (scope_options[0] if scope_options else [])
|
|
return OAuthToken(access_token="recorded-token", scope=" ".join(resolved_scopes))
|
|
|
|
|
|
def test_notification_wrapper_returns_pydantic_model(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/commerce/notification/v1/topic?limit=10",
|
|
json={"topics": [{"topicId": "MARKETPLACE_ACCOUNT_DELETION", "description": "topic"}], "total": 1},
|
|
)
|
|
|
|
client = NotificationClient(build_transport())
|
|
result = client.get_topics(limit=10)
|
|
|
|
assert isinstance(result, TopicSearchResponse)
|
|
assert result.total == 1
|
|
assert result.topics and result.topics[0].topicId == "MARKETPLACE_ACCOUNT_DELETION"
|
|
request = httpx_mock.get_requests()[0]
|
|
assert request.headers["Authorization"] == "Bearer test-token"
|
|
|
|
|
|
def test_notification_wrapper_serializes_typed_request_model(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/commerce/notification/v1/destination",
|
|
json={},
|
|
status_code=201,
|
|
)
|
|
|
|
client = NotificationClient(build_transport())
|
|
payload = DestinationRequest(
|
|
name="main-destination",
|
|
status="ENABLED",
|
|
deliveryConfig=DeliveryConfig(
|
|
endpoint="https://example.test/webhook",
|
|
verificationToken="verification_token_1234567890123456",
|
|
),
|
|
)
|
|
|
|
client.create_destination(payload)
|
|
|
|
request = httpx_mock.get_requests()[0]
|
|
body = json.loads(request.content.decode("utf-8"))
|
|
assert body["name"] == "main-destination"
|
|
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",
|
|
url="https://api.ebay.com/sell/inventory/v1/inventory_item/SKU-1",
|
|
json={"sku": "SKU-1"},
|
|
)
|
|
|
|
client = InventoryClient(build_transport())
|
|
result = client.get_inventory_item("SKU-1")
|
|
|
|
assert isinstance(result, InventoryItemWithSkuLocaleGroupid)
|
|
assert result.sku == "SKU-1"
|
|
|
|
|
|
def test_inventory_wrapper_supports_core_item_offer_and_group_workflows(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="PUT",
|
|
url="https://api.ebay.com/sell/inventory/v1/inventory_item/SKU-1",
|
|
json={"warnings": []},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="DELETE",
|
|
url="https://api.ebay.com/sell/inventory/v1/inventory_item/SKU-1",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="PUT",
|
|
url="https://api.ebay.com/sell/inventory/v1/inventory_item_group/GROUP-1",
|
|
json={"warnings": []},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/inventory/v1/inventory_item_group/GROUP-1",
|
|
json={"inventoryItemGroupKey": "GROUP-1", "variantSKUs": ["SKU-1"]},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/offer",
|
|
json={"offerId": "OFFER-1"},
|
|
status_code=201,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="PUT",
|
|
url="https://api.ebay.com/sell/inventory/v1/offer/OFFER-1",
|
|
json={"warnings": []},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/offer/OFFER-1/publish",
|
|
json={"offerId": "OFFER-1", "listingId": "ITEM-1"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/offer/OFFER-1/withdraw",
|
|
json={"offerId": "OFFER-1", "listingId": "ITEM-1"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/offer/publish_by_inventory_item_group",
|
|
json={"listingId": "ITEM-2"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/offer/withdraw_by_inventory_item_group",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/offer/get_listing_fees",
|
|
json={"feeSummaries": []},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="DELETE",
|
|
url="https://api.ebay.com/sell/inventory/v1/offer/OFFER-1",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="DELETE",
|
|
url="https://api.ebay.com/sell/inventory/v1/inventory_item_group/GROUP-1",
|
|
status_code=204,
|
|
)
|
|
|
|
client = InventoryClient(build_transport())
|
|
item_result = client.create_or_replace_inventory_item(
|
|
"SKU-1",
|
|
InventoryItem(condition="NEW"),
|
|
content_language="en-US",
|
|
)
|
|
client.delete_inventory_item("SKU-1")
|
|
group_result = client.create_or_replace_inventory_item_group(
|
|
"GROUP-1",
|
|
InventoryItemGroup(variantSKUs=["SKU-1"]),
|
|
content_language="en-US",
|
|
)
|
|
group = client.get_inventory_item_group("GROUP-1")
|
|
offer = client.create_offer(
|
|
EbayOfferDetailsWithKeys(sku="SKU-1", marketplaceId="EBAY_US", format="FIXED_PRICE"),
|
|
content_language="en-US",
|
|
)
|
|
updated_offer = client.update_offer(
|
|
"OFFER-1",
|
|
EbayOfferDetailsWithId(availableQuantity=2),
|
|
content_language="en-US",
|
|
)
|
|
published_offer = client.publish_offer("OFFER-1")
|
|
withdrawn_offer = client.withdraw_offer("OFFER-1")
|
|
group_publish = client.publish_offer_by_inventory_item_group(
|
|
PublishByInventoryItemGroupRequest(inventoryItemGroupKey="GROUP-1", marketplaceId="EBAY_US")
|
|
)
|
|
client.withdraw_offer_by_inventory_item_group(
|
|
WithdrawByInventoryItemGroupRequest(inventoryItemGroupKey="GROUP-1", marketplaceId="EBAY_US")
|
|
)
|
|
fees = client.get_listing_fees(OfferKeysWithId(offers=[OfferKeyWithId(offerId="OFFER-1")]))
|
|
client.delete_offer("OFFER-1")
|
|
client.delete_inventory_item_group("GROUP-1")
|
|
|
|
assert isinstance(item_result, BaseResponse)
|
|
assert isinstance(group_result, BaseResponse)
|
|
assert isinstance(group, InventoryItemGroup)
|
|
assert group.inventoryItemGroupKey == "GROUP-1"
|
|
assert isinstance(offer, OfferResponse)
|
|
assert offer.offerId == "OFFER-1"
|
|
assert isinstance(updated_offer, OfferResponse)
|
|
assert isinstance(published_offer, PublishResponse)
|
|
assert published_offer.listingId == "ITEM-1"
|
|
assert isinstance(withdrawn_offer, WithdrawResponse)
|
|
assert withdrawn_offer.listingId == "ITEM-1"
|
|
assert isinstance(group_publish, PublishResponse)
|
|
assert group_publish.listingId == "ITEM-2"
|
|
assert isinstance(fees, FeesSummaryResponse)
|
|
|
|
item_request = httpx_mock.get_requests()[0]
|
|
assert item_request.headers["Content-Language"] == "en-US"
|
|
item_body = json.loads(item_request.content.decode("utf-8"))
|
|
assert item_body["condition"] == "NEW"
|
|
|
|
offer_request = httpx_mock.get_requests()[4]
|
|
assert offer_request.headers["Content-Language"] == "en-US"
|
|
offer_body = json.loads(offer_request.content.decode("utf-8"))
|
|
assert offer_body["sku"] == "SKU-1"
|
|
assert offer_body["marketplaceId"] == "EBAY_US"
|
|
|
|
update_request = httpx_mock.get_requests()[5]
|
|
update_body = json.loads(update_request.content.decode("utf-8"))
|
|
assert update_body["availableQuantity"] == 2
|
|
|
|
group_publish_request = httpx_mock.get_requests()[8]
|
|
assert group_publish_request.headers["Content-Type"] == "application/json"
|
|
group_publish_body = json.loads(group_publish_request.content.decode("utf-8"))
|
|
assert group_publish_body["inventoryItemGroupKey"] == "GROUP-1"
|
|
|
|
fees_request = httpx_mock.get_requests()[10]
|
|
fees_body = json.loads(fees_request.content.decode("utf-8"))
|
|
assert fees_body["offers"] == [{"offerId": "OFFER-1"}]
|
|
|
|
|
|
def test_inventory_wrapper_supports_location_management(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/location/LOC-1",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/inventory/v1/location/LOC-1",
|
|
json={"merchantLocationKey": "LOC-1", "name": "Main warehouse"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/location/LOC-1/update_location_details",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/location/LOC-1/disable",
|
|
json={},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/location/LOC-1/enable",
|
|
json={},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/inventory/v1/location?limit=25&offset=0",
|
|
json={"locations": [{"merchantLocationKey": "LOC-1"}], "total": 1},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="DELETE",
|
|
url="https://api.ebay.com/sell/inventory/v1/location/LOC-1",
|
|
status_code=204,
|
|
)
|
|
|
|
client = InventoryClient(build_transport())
|
|
client.create_inventory_location(
|
|
"LOC-1",
|
|
InventoryLocationFull(location=LocationDetails(address=Address(country="US", postalCode="10001"))),
|
|
)
|
|
location = client.get_inventory_location("LOC-1")
|
|
client.update_inventory_location("LOC-1", InventoryLocation(name="Main warehouse"))
|
|
client.disable_inventory_location("LOC-1")
|
|
client.enable_inventory_location("LOC-1")
|
|
locations = client.get_inventory_locations(limit=25, offset=0)
|
|
client.delete_inventory_location("LOC-1")
|
|
|
|
assert isinstance(location, InventoryLocationResponse)
|
|
assert location.merchantLocationKey == "LOC-1"
|
|
assert isinstance(locations, LocationResponse)
|
|
assert locations.total == 1
|
|
|
|
create_request = httpx_mock.get_requests()[0]
|
|
create_body = json.loads(create_request.content.decode("utf-8"))
|
|
assert create_body["location"]["address"]["country"] == "US"
|
|
|
|
update_request = httpx_mock.get_requests()[2]
|
|
update_body = json.loads(update_request.content.decode("utf-8"))
|
|
assert update_body["name"] == "Main warehouse"
|
|
|
|
|
|
def test_inventory_wrapper_supports_bulk_and_auxiliary_endpoints(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/bulk_create_or_replace_inventory_item",
|
|
json={"responses": [{"sku": "SKU-2", "statusCode": 200}]},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/bulk_get_inventory_item",
|
|
json={"responses": [{"sku": "SKU-2"}]},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/bulk_update_price_quantity",
|
|
json={"responses": [{"offerId": "OFFER-2", "sku": "SKU-2", "statusCode": 200}]},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/inventory/v1/inventory_item/SKU-2/product_compatibility",
|
|
json={"sku": "SKU-2", "compatibleProducts": [{"compatibilityProperties": [{"name": "make", "value": "Toyota"}]}]},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="PUT",
|
|
url="https://api.ebay.com/sell/inventory/v1/inventory_item/SKU-2/product_compatibility",
|
|
json={"warnings": []},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="DELETE",
|
|
url="https://api.ebay.com/sell/inventory/v1/inventory_item/SKU-2/product_compatibility",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/bulk_migrate_listing",
|
|
json={"responses": [{"listingId": "ITEM-1", "statusCode": 200}]},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/inventory/v1/listing/ITEM-1/sku/SKU-2/locations",
|
|
json={"locations": [{"merchantLocationKey": "FC-1"}]},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="PUT",
|
|
url="https://api.ebay.com/sell/inventory/v1/listing/ITEM-1/sku/SKU-2/locations",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="DELETE",
|
|
url="https://api.ebay.com/sell/inventory/v1/listing/ITEM-1/sku/SKU-2/locations",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/bulk_create_offer",
|
|
json={"responses": [{"offerId": "OFFER-2", "sku": "SKU-2", "marketplaceId": "EBAY_US"}]},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/inventory/v1/bulk_publish_offer",
|
|
json={"responses": [{"offerId": "OFFER-2", "listingId": "ITEM-2", "statusCode": 200}]},
|
|
)
|
|
|
|
client = InventoryClient(build_transport())
|
|
bulk_item_response = client.bulk_create_or_replace_inventory_item(
|
|
BulkInventoryItem(
|
|
requests=[InventoryItemWithSkuLocale(sku="SKU-2", locale="en_US", condition="NEW")]
|
|
),
|
|
content_language="en-US",
|
|
)
|
|
bulk_get_response = client.bulk_get_inventory_item(
|
|
BulkGetInventoryItem(requests=[GetInventoryItem(sku="SKU-2")])
|
|
)
|
|
bulk_price_response = client.bulk_update_price_quantity(
|
|
BulkPriceQuantity(
|
|
requests=[
|
|
{
|
|
"sku": "SKU-2",
|
|
"shipToLocationAvailability": {"quantity": 5},
|
|
"offers": [{"offerId": "OFFER-2", "availableQuantity": 2}],
|
|
}
|
|
]
|
|
)
|
|
)
|
|
compatibility = client.get_product_compatibility("SKU-2")
|
|
compatibility_result = client.create_or_replace_product_compatibility(
|
|
"SKU-2",
|
|
Compatibility(compatibleProducts=[{"compatibilityProperties": [{"name": "make", "value": "Toyota"}]}]),
|
|
content_language="en-US",
|
|
)
|
|
client.delete_product_compatibility("SKU-2")
|
|
migrate_response = client.bulk_migrate_listing(BulkMigrateListing(requests=[MigrateListing(listingId="ITEM-1")]))
|
|
location_mapping = client.get_sku_location_mapping("ITEM-1", "SKU-2")
|
|
client.create_or_replace_sku_location_mapping(
|
|
"ITEM-1",
|
|
"SKU-2",
|
|
LocationMapping(locations=[LocationAvailabilityDetails(merchantLocationKey="FC-1")]),
|
|
)
|
|
client.delete_sku_location_mapping("ITEM-1", "SKU-2")
|
|
bulk_offer_response = client.bulk_create_offer(
|
|
BulkEbayOfferDetailsWithKeys(
|
|
requests=[EbayOfferDetailsWithKeys(sku="SKU-2", marketplaceId="EBAY_US", format="FIXED_PRICE")]
|
|
),
|
|
content_language="en-US",
|
|
)
|
|
bulk_publish_response = client.bulk_publish_offer(
|
|
BulkOffer(requests=[OfferKeyWithId(offerId="OFFER-2")])
|
|
)
|
|
|
|
assert isinstance(bulk_item_response, BulkInventoryItemResponse)
|
|
assert bulk_item_response.responses and bulk_item_response.responses[0].sku == "SKU-2"
|
|
assert isinstance(bulk_get_response, BulkGetInventoryItemResponse)
|
|
assert bulk_get_response.responses and bulk_get_response.responses[0].sku == "SKU-2"
|
|
assert isinstance(bulk_price_response, BulkPriceQuantityResponse)
|
|
assert bulk_price_response.responses and bulk_price_response.responses[0].offerId == "OFFER-2"
|
|
assert isinstance(compatibility, Compatibility)
|
|
assert compatibility.sku == "SKU-2"
|
|
assert isinstance(compatibility_result, BaseResponse)
|
|
assert isinstance(migrate_response, BulkMigrateListingResponse)
|
|
assert migrate_response.responses and migrate_response.responses[0].listingId == "ITEM-1"
|
|
assert isinstance(location_mapping, LocationMapping)
|
|
assert location_mapping.locations and location_mapping.locations[0].merchantLocationKey == "FC-1"
|
|
assert isinstance(bulk_offer_response, BulkOfferResponse)
|
|
assert bulk_offer_response.responses and bulk_offer_response.responses[0].offerId == "OFFER-2"
|
|
assert isinstance(bulk_publish_response, BulkPublishResponse)
|
|
assert bulk_publish_response.responses and bulk_publish_response.responses[0].listingId == "ITEM-2"
|
|
|
|
bulk_item_request = httpx_mock.get_requests()[0]
|
|
assert bulk_item_request.headers["Content-Language"] == "en-US"
|
|
bulk_item_body = json.loads(bulk_item_request.content.decode("utf-8"))
|
|
assert bulk_item_body["requests"][0]["sku"] == "SKU-2"
|
|
|
|
bulk_get_request = httpx_mock.get_requests()[1]
|
|
bulk_get_body = json.loads(bulk_get_request.content.decode("utf-8"))
|
|
assert bulk_get_body["requests"][0]["sku"] == "SKU-2"
|
|
|
|
bulk_price_request = httpx_mock.get_requests()[2]
|
|
bulk_price_body = json.loads(bulk_price_request.content.decode("utf-8"))
|
|
assert bulk_price_body["requests"][0]["sku"] == "SKU-2"
|
|
assert bulk_price_body["requests"][0]["offers"][0]["offerId"] == "OFFER-2"
|
|
|
|
compatibility_request = httpx_mock.get_requests()[4]
|
|
assert compatibility_request.headers["Content-Language"] == "en-US"
|
|
|
|
location_mapping_request = httpx_mock.get_requests()[8]
|
|
location_mapping_body = json.loads(location_mapping_request.content.decode("utf-8"))
|
|
assert location_mapping_body["locations"][0]["merchantLocationKey"] == "FC-1"
|
|
|
|
bulk_offer_request = httpx_mock.get_requests()[10]
|
|
assert bulk_offer_request.headers["Content-Language"] == "en-US"
|
|
bulk_offer_body = json.loads(bulk_offer_request.content.decode("utf-8"))
|
|
assert bulk_offer_body["requests"][0]["sku"] == "SKU-2"
|
|
|
|
bulk_publish_request = httpx_mock.get_requests()[11]
|
|
bulk_publish_body = json.loads(bulk_publish_request.content.decode("utf-8"))
|
|
assert bulk_publish_body["requests"] == [{"offerId": "OFFER-2"}]
|
|
|
|
|
|
def test_fulfillment_wrapper_returns_order_model(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/fulfillment/v1/order/ORDER-1",
|
|
json={"orderId": "ORDER-1"},
|
|
)
|
|
|
|
client = FulfillmentClient(build_transport())
|
|
result = client.get_order("ORDER-1")
|
|
|
|
assert isinstance(result, Order)
|
|
assert result.orderId == "ORDER-1"
|
|
|
|
|
|
def test_fulfillment_wrapper_supports_refund_and_shipping_creation(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/fulfillment/v1/order/ORDER-1/issue_refund",
|
|
json={"refundId": "REFUND-1", "refundStatus": "PENDING"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/fulfillment/v1/order/ORDER-1/shipping_fulfillment",
|
|
status_code=201,
|
|
headers={
|
|
"Location": "https://api.ebay.com/sell/fulfillment/v1/order/ORDER-1/shipping_fulfillment/FULFILL-1"
|
|
},
|
|
json={},
|
|
)
|
|
|
|
client = FulfillmentClient(build_transport())
|
|
refund = client.issue_refund(
|
|
"ORDER-1",
|
|
IssueRefundRequest(reasonForRefund="BUYER_CANCELLED", comment="requested", orderLevelRefundAmount={"currency": "USD", "value": "10.00"}),
|
|
)
|
|
fulfillment = client.create_shipping_fulfillment(
|
|
"ORDER-1",
|
|
ShippingFulfillmentDetails(
|
|
lineItems=[LineItemReference(lineItemId="LINE-1", quantity=1)],
|
|
shippingCarrierCode="USPS",
|
|
trackingNumber="TRACK123",
|
|
),
|
|
)
|
|
|
|
assert isinstance(refund, Refund)
|
|
assert refund.refundId == "REFUND-1"
|
|
assert isinstance(fulfillment, CreatedShippingFulfillment)
|
|
assert fulfillment.fulfillment_id == "FULFILL-1"
|
|
|
|
refund_request = httpx_mock.get_requests()[0]
|
|
refund_body = json.loads(refund_request.content.decode("utf-8"))
|
|
assert refund_body["reasonForRefund"] == "BUYER_CANCELLED"
|
|
assert refund_request.headers["Authorization"] == "Bearer test-token"
|
|
|
|
shipping_request = httpx_mock.get_requests()[1]
|
|
shipping_body = json.loads(shipping_request.content.decode("utf-8"))
|
|
assert shipping_body["shippingCarrierCode"] == "USPS"
|
|
assert shipping_body["trackingNumber"] == "TRACK123"
|
|
|
|
|
|
def test_fulfillment_wrapper_supports_payment_dispute_workflow(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://apiz.ebay.com/sell/fulfillment/v1/payment_dispute/DISPUTE-1",
|
|
json={"paymentDisputeId": "DISPUTE-1", "revision": 3},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://apiz.ebay.com/sell/fulfillment/v1/payment_dispute/DISPUTE-1/activity",
|
|
json={"activities": []},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url=(
|
|
"https://apiz.ebay.com/sell/fulfillment/v1/payment_dispute_summary?buyer_username=buyer1"
|
|
"&payment_dispute_status=OPEN&payment_dispute_status=ACTION_NEEDED&limit=25&offset=0"
|
|
),
|
|
json={"paymentDisputeSummaries": [], "total": 0},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://apiz.ebay.com/sell/fulfillment/v1/payment_dispute/DISPUTE-1/contest",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://apiz.ebay.com/sell/fulfillment/v1/payment_dispute/DISPUTE-1/accept",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://apiz.ebay.com/sell/fulfillment/v1/payment_dispute/DISPUTE-1/upload_evidence_file",
|
|
json={"fileId": "FILE-1", "fileName": "label.jpg"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://apiz.ebay.com/sell/fulfillment/v1/payment_dispute/DISPUTE-1/add_evidence",
|
|
json={"evidenceId": "EVID-1"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://apiz.ebay.com/sell/fulfillment/v1/payment_dispute/DISPUTE-1/update_evidence",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://apiz.ebay.com/sell/fulfillment/v1/payment_dispute/DISPUTE-1/fetch_evidence_content?evidence_id=EVID-1&file_id=FILE-1",
|
|
status_code=200,
|
|
headers={"content-disposition": 'attachment; filename="label.jpg"'},
|
|
content=b"binary-evidence",
|
|
)
|
|
|
|
client = FulfillmentClient(build_transport())
|
|
dispute = client.get_payment_dispute("DISPUTE-1")
|
|
activities = client.get_payment_dispute_activities("DISPUTE-1")
|
|
summaries = client.get_payment_dispute_summaries(
|
|
buyer_username="buyer1",
|
|
payment_dispute_status=["OPEN", "ACTION_NEEDED"],
|
|
limit=25,
|
|
offset=0,
|
|
)
|
|
client.contest_payment_dispute("DISPUTE-1", ContestPaymentDisputeRequest(revision=3, note="tracking attached"))
|
|
client.accept_payment_dispute("DISPUTE-1", AcceptPaymentDisputeRequest(revision=3))
|
|
file_evidence = client.upload_evidence_file(
|
|
"DISPUTE-1",
|
|
file_name="label.jpg",
|
|
content=b"jpg-bytes",
|
|
content_type="image/jpeg",
|
|
)
|
|
evidence_response = client.add_evidence(
|
|
"DISPUTE-1",
|
|
AddEvidencePaymentDisputeRequest(
|
|
evidenceType="PROOF_OF_DELIVERY",
|
|
files=[FileEvidence(fileId="FILE-1")],
|
|
lineItems=[OrderLineItems(lineItemId="LINE-1", itemId="ITEM-1")],
|
|
),
|
|
)
|
|
client.update_evidence(
|
|
"DISPUTE-1",
|
|
UpdateEvidencePaymentDisputeRequest(
|
|
evidenceId="EVID-1",
|
|
evidenceType="PROOF_OF_DELIVERY",
|
|
files=[FileEvidence(fileId="FILE-1")],
|
|
lineItems=[OrderLineItems(lineItemId="LINE-1", itemId="ITEM-1")],
|
|
),
|
|
)
|
|
evidence_file = client.fetch_evidence_content("DISPUTE-1", evidence_id="EVID-1", file_id="FILE-1")
|
|
|
|
assert isinstance(dispute, PaymentDispute)
|
|
assert isinstance(activities, PaymentDisputeActivityHistory)
|
|
assert isinstance(summaries, DisputeSummaryResponse)
|
|
assert summaries.total == 0
|
|
assert isinstance(file_evidence, FileEvidence)
|
|
assert file_evidence.fileId == "FILE-1"
|
|
assert isinstance(evidence_response, AddEvidencePaymentDisputeResponse)
|
|
assert evidence_response.evidenceId == "EVID-1"
|
|
assert isinstance(evidence_file, EvidenceFileDownload)
|
|
assert evidence_file.file_name == "label.jpg"
|
|
assert evidence_file.content == b"binary-evidence"
|
|
|
|
contest_request = httpx_mock.get_requests()[3]
|
|
assert contest_request.url.host == "apiz.ebay.com"
|
|
contest_body = json.loads(contest_request.content.decode("utf-8"))
|
|
assert contest_body["revision"] == 3
|
|
|
|
upload_request = httpx_mock.get_requests()[5]
|
|
assert upload_request.url.host == "apiz.ebay.com"
|
|
assert upload_request.headers["Content-Type"].startswith("multipart/form-data;")
|
|
|
|
|
|
def test_fulfillment_wrapper_accepts_readonly_or_full_scope_options_for_shipping_reads(httpx_mock: HTTPXMock) -> None:
|
|
oauth_client = RecordingOAuthClient()
|
|
transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/fulfillment/v1/order/ORDER-1/shipping_fulfillment/FULL-1",
|
|
json={"fulfillmentId": "FULL-1"},
|
|
)
|
|
|
|
FulfillmentClient(transport).get_shipping_fulfillment("ORDER-1", "FULL-1")
|
|
|
|
assert oauth_client.last_scopes is None
|
|
assert oauth_client.last_scope_options == [
|
|
["https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly"],
|
|
["https://api.ebay.com/oauth/api_scope/sell.fulfillment"],
|
|
]
|
|
|
|
|
|
def test_account_wrapper_returns_programs_model(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/account/v1/program/get_opted_in_programs",
|
|
json={"programs": [{"programType": "OUT_OF_STOCK_CONTROL"}]},
|
|
)
|
|
|
|
client = AccountClient(build_transport())
|
|
result = client.get_opted_in_programs()
|
|
|
|
assert isinstance(result, Programs)
|
|
assert result.programs and result.programs[0].programType == "OUT_OF_STOCK_CONTROL"
|
|
|
|
|
|
def test_account_wrapper_returns_policy_models_by_id_and_name(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/account/v1/fulfillment_policy/FULFILL-1",
|
|
json={"fulfillmentPolicyId": "FULFILL-1", "name": "Fast shipping"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/account/v1/payment_policy/get_by_policy_name?marketplace_id=EBAY_US&name=Default%20payment",
|
|
json={"paymentPolicyId": "PAY-1", "name": "Default payment"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/account/v1/return_policy/get_by_policy_name?marketplace_id=EBAY_US&name=30%20day%20returns",
|
|
json={"returnPolicyId": "RET-1", "name": "30 day returns"},
|
|
)
|
|
|
|
client = AccountClient(build_transport())
|
|
fulfillment = client.get_fulfillment_policy("FULFILL-1")
|
|
payment = client.get_payment_policy_by_name(marketplace_id="EBAY_US", name="Default payment")
|
|
returns = client.get_return_policy_by_name(marketplace_id="EBAY_US", name="30 day returns")
|
|
|
|
assert isinstance(fulfillment, FulfillmentPolicy)
|
|
assert fulfillment.fulfillmentPolicyId == "FULFILL-1"
|
|
assert isinstance(payment, PaymentPolicy)
|
|
assert payment.paymentPolicyId == "PAY-1"
|
|
assert isinstance(returns, ReturnPolicy)
|
|
assert returns.returnPolicyId == "RET-1"
|
|
|
|
|
|
def test_account_wrapper_serializes_policy_requests_and_delete(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/account/v1/payment_policy",
|
|
json={"paymentPolicyId": "PAY-NEW"},
|
|
status_code=201,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="PUT",
|
|
url="https://api.ebay.com/sell/account/v1/fulfillment_policy/FULFILL-1",
|
|
json={"fulfillmentPolicyId": "FULFILL-1"},
|
|
status_code=200,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="DELETE",
|
|
url="https://api.ebay.com/sell/account/v1/return_policy/RET-1",
|
|
status_code=204,
|
|
)
|
|
|
|
client = AccountClient(build_transport())
|
|
payment_payload = PaymentPolicyRequest(name="Default payment")
|
|
fulfillment_payload = FulfillmentPolicyRequest(name="Fast shipping")
|
|
|
|
created_payment = client.create_payment_policy(payment_payload)
|
|
updated_fulfillment = client.update_fulfillment_policy("FULFILL-1", fulfillment_payload)
|
|
client.delete_return_policy("RET-1")
|
|
|
|
assert created_payment.paymentPolicyId == "PAY-NEW"
|
|
assert updated_fulfillment.fulfillmentPolicyId == "FULFILL-1"
|
|
|
|
create_request = httpx_mock.get_requests()[0]
|
|
create_body = json.loads(create_request.content.decode("utf-8"))
|
|
assert create_body["name"] == "Default payment"
|
|
|
|
update_request = httpx_mock.get_requests()[1]
|
|
update_body = json.loads(update_request.content.decode("utf-8"))
|
|
assert update_body["name"] == "Fast shipping"
|
|
|
|
|
|
def test_account_wrapper_supports_return_policy_create_and_update(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/account/v1/return_policy",
|
|
json={"returnPolicyId": "RET-NEW"},
|
|
status_code=201,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="PUT",
|
|
url="https://api.ebay.com/sell/account/v1/return_policy/RET-NEW",
|
|
json={"returnPolicyId": "RET-NEW"},
|
|
status_code=200,
|
|
)
|
|
|
|
client = AccountClient(build_transport())
|
|
payload = ReturnPolicyRequest(name="30 day returns")
|
|
|
|
created = client.create_return_policy(payload)
|
|
updated = client.update_return_policy("RET-NEW", payload)
|
|
|
|
assert created.returnPolicyId == "RET-NEW"
|
|
assert updated.returnPolicyId == "RET-NEW"
|
|
|
|
|
|
def test_feed_wrapper_returns_task_collection_model(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/feed/v1/task?feed_type=LMS_ORDER_REPORT",
|
|
json={"tasks": [{"taskId": "TASK-1"}], "total": 1},
|
|
)
|
|
|
|
client = FeedClient(build_transport())
|
|
result = client.get_tasks(feed_type="LMS_ORDER_REPORT")
|
|
|
|
assert isinstance(result, TaskCollection)
|
|
assert result.total == 1
|
|
assert result.tasks and result.tasks[0].taskId == "TASK-1"
|
|
|
|
|
|
def test_feed_wrapper_supports_schedule_crud_and_template_lookup(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/feed/v1/schedule",
|
|
status_code=201,
|
|
headers={"Location": "https://api.ebay.com/sell/feed/v1/schedule/SCHEDULE-1"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/feed/v1/schedule/SCHEDULE-1",
|
|
json={"scheduleId": "SCHEDULE-1", "feedType": "LMS_ORDER_REPORT"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="PUT",
|
|
url="https://api.ebay.com/sell/feed/v1/schedule/SCHEDULE-1",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="DELETE",
|
|
url="https://api.ebay.com/sell/feed/v1/schedule/SCHEDULE-1",
|
|
status_code=204,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/feed/v1/schedule_template/TEMPLATE-1",
|
|
json={"scheduleTemplateId": "TEMPLATE-1", "feedType": "LMS_ORDER_REPORT"},
|
|
)
|
|
|
|
client = FeedClient(build_transport())
|
|
created = client.create_schedule(
|
|
CreateUserScheduleRequest(feedType="LMS_ORDER_REPORT", scheduleTemplateId="TEMPLATE-1")
|
|
)
|
|
schedule = client.get_schedule("SCHEDULE-1")
|
|
client.update_schedule("SCHEDULE-1", UpdateUserScheduleRequest(scheduleName="nightly"))
|
|
client.delete_schedule("SCHEDULE-1")
|
|
template = client.get_schedule_template("TEMPLATE-1")
|
|
|
|
assert isinstance(created, CreatedFeedResource)
|
|
assert created.resource_id == "SCHEDULE-1"
|
|
assert isinstance(schedule, UserScheduleResponse)
|
|
assert schedule.scheduleId == "SCHEDULE-1"
|
|
assert isinstance(template, ScheduleTemplateResponse)
|
|
assert template.scheduleTemplateId == "TEMPLATE-1"
|
|
|
|
create_body = json.loads(httpx_mock.get_requests()[0].content.decode("utf-8"))
|
|
assert create_body["feedType"] == "LMS_ORDER_REPORT"
|
|
update_body = json.loads(httpx_mock.get_requests()[2].content.decode("utf-8"))
|
|
assert update_body["scheduleName"] == "nightly"
|
|
|
|
|
|
def test_feed_wrapper_supports_task_create_upload_and_file_downloads(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/feed/v1/task",
|
|
status_code=202,
|
|
headers={"Location": "https://api.ebay.com/sell/feed/v1/task/TASK-2"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/sell/feed/v1/task/TASK-2/upload_file",
|
|
status_code=200,
|
|
json={},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/feed/v1/task/TASK-2/download_input_file",
|
|
status_code=200,
|
|
headers={"content-disposition": 'attachment; filename="input.xml"'},
|
|
content=b"<input />",
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/feed/v1/task/TASK-2/download_result_file",
|
|
status_code=200,
|
|
headers={"content-disposition": 'attachment; filename="result.csv"'},
|
|
content=b"id,name\n1,demo\n",
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/feed/v1/schedule/SCHEDULE-9/download_result_file",
|
|
status_code=200,
|
|
headers={"content-disposition": 'attachment; filename="latest.csv"'},
|
|
content=b"id,name\n2,latest\n",
|
|
)
|
|
|
|
client = FeedClient(build_transport())
|
|
created = client.create_task(
|
|
CreateTaskRequest(feedType="LMS_ORDER_REPORT", schemaVersion="1.0"),
|
|
marketplace_id="EBAY_US",
|
|
)
|
|
client.upload_file("TASK-2", file_name="input.xml", content=b"<input />", content_type="application/xml")
|
|
input_file = client.get_input_file("TASK-2")
|
|
result_file = client.get_result_file("TASK-2")
|
|
latest_file = client.get_latest_result_file("SCHEDULE-9")
|
|
|
|
assert isinstance(created, CreatedFeedResource)
|
|
assert created.resource_id == "TASK-2"
|
|
assert isinstance(input_file, FeedFileDownload)
|
|
assert input_file.file_name == "input.xml"
|
|
assert input_file.content == b"<input />"
|
|
assert result_file.file_name == "result.csv"
|
|
assert result_file.content.startswith(b"id,name")
|
|
assert latest_file.file_name == "latest.csv"
|
|
|
|
create_request = httpx_mock.get_requests()[0]
|
|
assert create_request.headers["X-EBAY-C-MARKETPLACE-ID"] == "EBAY_US"
|
|
create_body = json.loads(create_request.content.decode("utf-8"))
|
|
assert create_body["feedType"] == "LMS_ORDER_REPORT"
|
|
|
|
upload_request = httpx_mock.get_requests()[1]
|
|
assert upload_request.headers["Content-Type"].startswith("multipart/form-data;")
|
|
assert b"filename=\"input.xml\"" in upload_request.content
|
|
|
|
|
|
def test_feed_wrapper_passes_task_and_schedule_filters(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url=(
|
|
"https://api.ebay.com/sell/feed/v1/task?feed_type=LMS_ORDER_REPORT"
|
|
"&schedule_id=SCHEDULE-1&date_range=2026-01-01T00:00:00.000Z..2026-01-02T00:00:00.000Z"
|
|
"&look_back_days=7&limit=50&offset=0"
|
|
),
|
|
json={"tasks": [], "total": 0},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/feed/v1/schedule?feed_type=LMS_ORDER_REPORT&limit=25&offset=0",
|
|
json={"schedules": [], "total": 0},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/feed/v1/schedule_template?feed_type=LMS_ORDER_REPORT&limit=25&offset=0",
|
|
json={"scheduleTemplates": [], "total": 0},
|
|
)
|
|
|
|
client = FeedClient(build_transport())
|
|
tasks = client.get_tasks(
|
|
feed_type="LMS_ORDER_REPORT",
|
|
schedule_id="SCHEDULE-1",
|
|
date_range="2026-01-01T00:00:00.000Z..2026-01-02T00:00:00.000Z",
|
|
look_back_days=7,
|
|
limit=50,
|
|
offset=0,
|
|
)
|
|
schedules = client.get_schedules(feed_type="LMS_ORDER_REPORT", limit=25, offset=0)
|
|
templates = client.get_schedule_templates(feed_type="LMS_ORDER_REPORT", limit=25, offset=0)
|
|
|
|
assert tasks.total == 0
|
|
assert schedules.total == 0
|
|
assert templates.total == 0
|
|
|
|
|
|
def test_inventory_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
|
|
oauth_client = RecordingOAuthClient()
|
|
transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/inventory/v1/inventory_item/SKU-1",
|
|
json={"sku": "SKU-1"},
|
|
)
|
|
|
|
InventoryClient(transport).get_inventory_item("SKU-1")
|
|
|
|
assert oauth_client.last_scopes is None
|
|
assert oauth_client.last_scope_options == [
|
|
["https://api.ebay.com/oauth/api_scope/sell.inventory.readonly"],
|
|
["https://api.ebay.com/oauth/api_scope/sell.inventory"],
|
|
]
|
|
|
|
|
|
def test_fulfillment_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
|
|
oauth_client = RecordingOAuthClient()
|
|
transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/fulfillment/v1/order/ORDER-1",
|
|
json={"orderId": "ORDER-1"},
|
|
)
|
|
|
|
FulfillmentClient(transport).get_order("ORDER-1")
|
|
|
|
assert oauth_client.last_scopes is None
|
|
assert oauth_client.last_scope_options == [
|
|
["https://api.ebay.com/oauth/api_scope/sell.fulfillment.readonly"],
|
|
["https://api.ebay.com/oauth/api_scope/sell.fulfillment"],
|
|
]
|
|
|
|
|
|
def test_account_wrapper_accepts_readonly_or_full_scope_options(httpx_mock: HTTPXMock) -> None:
|
|
oauth_client = RecordingOAuthClient()
|
|
transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/account/v1/privilege",
|
|
json={},
|
|
)
|
|
|
|
AccountClient(transport).get_privileges()
|
|
|
|
assert oauth_client.last_scopes is None
|
|
assert oauth_client.last_scope_options == [
|
|
["https://api.ebay.com/oauth/api_scope/sell.account.readonly"],
|
|
["https://api.ebay.com/oauth/api_scope/sell.account"],
|
|
]
|
|
|
|
|
|
def test_feed_wrapper_accepts_any_documented_feed_scope_option(httpx_mock: HTTPXMock) -> None:
|
|
oauth_client = RecordingOAuthClient()
|
|
transport = ApiTransport(base_url="https://api.ebay.com", oauth_client=oauth_client)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/sell/feed/v1/task?feed_type=LMS_ORDER_REPORT",
|
|
json={"tasks": [{"taskId": "TASK-1"}], "total": 1},
|
|
)
|
|
|
|
FeedClient(transport).get_tasks(feed_type="LMS_ORDER_REPORT")
|
|
|
|
assert oauth_client.last_scopes is None
|
|
assert oauth_client.last_scope_options == [
|
|
["https://api.ebay.com/oauth/api_scope/sell.inventory"],
|
|
["https://api.ebay.com/oauth/api_scope/sell.fulfillment"],
|
|
["https://api.ebay.com/oauth/api_scope/sell.marketing"],
|
|
["https://api.ebay.com/oauth/api_scope/commerce.catalog.readonly"],
|
|
["https://api.ebay.com/oauth/api_scope/sell.analytics.readonly"],
|
|
]
|
|
|
|
|
|
def test_media_wrapper_returns_image_model_from_url(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/commerce/media/v1_beta/image/create_image_from_url",
|
|
json={"imageUrl": "https://i.ebayimg.com/images/g/demo.jpg", "expirationDate": "2026-12-31T00:00:00Z"},
|
|
status_code=201,
|
|
)
|
|
|
|
client = MediaClient(build_transport())
|
|
result = client.create_image_from_url(
|
|
CreateImageFromUrlRequest(imageUrl="https://example.test/demo.jpg")
|
|
)
|
|
|
|
assert isinstance(result, ImageResponse)
|
|
assert result.imageUrl == "https://i.ebayimg.com/images/g/demo.jpg"
|
|
request = httpx_mock.get_requests()[0]
|
|
body = json.loads(request.content.decode("utf-8"))
|
|
assert body["imageUrl"] == "https://example.test/demo.jpg"
|
|
|
|
|
|
def test_media_wrapper_serializes_multipart_image_upload(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/commerce/media/v1_beta/image/create_image_from_file",
|
|
json={"imageUrl": "https://i.ebayimg.com/images/g/uploaded.jpg"},
|
|
status_code=201,
|
|
)
|
|
|
|
client = MediaClient(build_transport())
|
|
result = client.create_image_from_file(
|
|
file_name="demo.jpg",
|
|
content=b"binary-image-content",
|
|
content_type="image/jpeg",
|
|
)
|
|
|
|
assert isinstance(result, ImageResponse)
|
|
request = httpx_mock.get_requests()[0]
|
|
assert request.headers["Content-Type"].startswith("multipart/form-data;")
|
|
assert b"name=\"image\"" in request.content
|
|
assert b"filename=\"demo.jpg\"" in request.content
|
|
assert b"binary-image-content" in request.content
|
|
|
|
|
|
def test_media_wrapper_returns_created_resource_location_for_video(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/commerce/media/v1_beta/video",
|
|
status_code=201,
|
|
headers={"Location": "https://api.ebay.com/commerce/media/v1_beta/video/VIDEO-1"},
|
|
)
|
|
|
|
client = MediaClient(build_transport())
|
|
result = client.create_video(
|
|
CreateVideoRequest(title="Demo", size=1024, classification=["ITEM"])
|
|
)
|
|
|
|
assert isinstance(result, CreatedMediaResource)
|
|
assert result.location == "https://api.ebay.com/commerce/media/v1_beta/video/VIDEO-1"
|
|
assert result.resource_id == "VIDEO-1"
|
|
|
|
|
|
def test_extract_media_resource_id_handles_location_header() -> None:
|
|
assert extract_resource_id("https://api.ebay.com/commerce/media/v1_beta/video/VIDEO-1") == "VIDEO-1"
|
|
assert extract_resource_id("https://api.ebay.com/commerce/media/v1_beta/document/DOC-1/") == "DOC-1"
|
|
assert extract_resource_id(None) is None
|
|
|
|
|
|
def test_media_wrapper_returns_video_model(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/commerce/media/v1_beta/video/VIDEO-1",
|
|
json={"videoId": "VIDEO-1", "status": "LIVE", "title": "Demo"},
|
|
)
|
|
|
|
client = MediaClient(build_transport())
|
|
result = client.get_video("VIDEO-1")
|
|
|
|
assert isinstance(result, Video)
|
|
assert result.videoId == "VIDEO-1"
|
|
|
|
|
|
def test_media_wrapper_uploads_video_bytes(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/commerce/media/v1_beta/video/VIDEO-1/upload",
|
|
status_code=200,
|
|
)
|
|
|
|
client = MediaClient(build_transport())
|
|
client.upload_video(
|
|
"VIDEO-1",
|
|
content=b"video-bytes",
|
|
content_length=11,
|
|
content_range="bytes 0-10/11",
|
|
)
|
|
|
|
request = httpx_mock.get_requests()[0]
|
|
assert request.headers["Content-Type"] == "application/octet-stream"
|
|
assert request.headers["Content-Length"] == "11"
|
|
assert request.headers["Content-Range"] == "bytes 0-10/11"
|
|
assert request.content == b"video-bytes"
|
|
|
|
|
|
def test_media_wrapper_returns_document_models(httpx_mock: HTTPXMock) -> None:
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/commerce/media/v1_beta/document",
|
|
json={"documentId": "DOC-1", "documentStatus": "PENDING_UPLOAD"},
|
|
status_code=201,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/commerce/media/v1_beta/document/create_document_from_url",
|
|
json={"documentId": "DOC-2", "documentStatus": "SUBMITTED"},
|
|
status_code=201,
|
|
)
|
|
httpx_mock.add_response(
|
|
method="GET",
|
|
url="https://api.ebay.com/commerce/media/v1_beta/document/DOC-1",
|
|
json={"documentId": "DOC-1", "documentStatus": "ACCEPTED", "documentType": "USER_GUIDE_OR_MANUAL"},
|
|
)
|
|
httpx_mock.add_response(
|
|
method="POST",
|
|
url="https://api.ebay.com/commerce/media/v1_beta/document/DOC-1/upload",
|
|
json={"documentId": "DOC-1", "documentStatus": "SUBMITTED"},
|
|
status_code=200,
|
|
)
|
|
|
|
client = MediaClient(build_transport())
|
|
created = client.create_document(
|
|
CreateDocumentRequest(documentType="USER_GUIDE_OR_MANUAL", languages=["en-US"])
|
|
)
|
|
created_from_url = client.create_document_from_url(
|
|
CreateDocumentFromUrlRequest(
|
|
documentType="USER_GUIDE_OR_MANUAL",
|
|
documentUrl="https://example.test/guide.pdf",
|
|
languages=["en-US"],
|
|
)
|
|
)
|
|
fetched = client.get_document("DOC-1")
|
|
uploaded = client.upload_document(
|
|
"DOC-1",
|
|
file_name="guide.pdf",
|
|
content=b"%PDF-1.7",
|
|
content_type="application/pdf",
|
|
)
|
|
|
|
assert created.documentId == "DOC-1"
|
|
assert created_from_url.documentId == "DOC-2"
|
|
assert isinstance(fetched, DocumentResponse)
|
|
assert fetched.documentStatus == "ACCEPTED"
|
|
assert uploaded.documentStatus == "SUBMITTED"
|
|
upload_request = httpx_mock.get_requests()[3]
|
|
assert upload_request.headers["Content-Type"].startswith("multipart/form-data;")
|
|
assert b"filename=\"guide.pdf\"" in upload_request.content
|
|
assert b"%PDF-1.7" in upload_request.content
|
|
|
|
|
|
def test_media_wait_for_video_returns_live_payload(monkeypatch) -> None:
|
|
client = MediaClient(build_transport())
|
|
states = iter(
|
|
[
|
|
Video(videoId="VIDEO-1", status="PENDING_UPLOAD"),
|
|
Video(videoId="VIDEO-1", status="PROCESSING"),
|
|
Video(videoId="VIDEO-1", status="LIVE"),
|
|
]
|
|
)
|
|
|
|
monkeypatch.setattr(client, "get_video", lambda _video_id: next(states))
|
|
monkeypatch.setattr("ebay_client.media.client.sleep", lambda _seconds: None)
|
|
|
|
result = client.wait_for_video("VIDEO-1", poll_interval_seconds=0.0)
|
|
|
|
assert result.status == "LIVE"
|
|
|
|
|
|
def test_media_wait_for_document_raises_on_terminal_failure(monkeypatch) -> None:
|
|
client = MediaClient(build_transport())
|
|
|
|
monkeypatch.setattr(
|
|
client,
|
|
"get_document",
|
|
lambda _document_id: DocumentResponse(documentId="DOC-1", documentStatus="REJECTED"),
|
|
)
|
|
|
|
try:
|
|
client.wait_for_document("DOC-1", poll_interval_seconds=0.0)
|
|
except ValueError as exc:
|
|
assert "REJECTED" in str(exc)
|
|
else:
|
|
raise AssertionError("Expected wait_for_document to raise on terminal failure status")
|
|
|
|
|
|
def test_media_create_upload_and_wait_video_orchestrates_flow(monkeypatch) -> None:
|
|
client = MediaClient(build_transport())
|
|
calls: list[tuple[str, object]] = []
|
|
|
|
monkeypatch.setattr(
|
|
client,
|
|
"create_video",
|
|
lambda payload: calls.append(("create_video", payload)) or CreatedMediaResource(resource_id="VIDEO-9"),
|
|
)
|
|
monkeypatch.setattr(
|
|
client,
|
|
"upload_video",
|
|
lambda video_id, **kwargs: calls.append(("upload_video", {"video_id": video_id, **kwargs})),
|
|
)
|
|
monkeypatch.setattr(
|
|
client,
|
|
"wait_for_video",
|
|
lambda video_id, **kwargs: calls.append(("wait_for_video", {"video_id": video_id, **kwargs}))
|
|
or Video(videoId=video_id, status="LIVE"),
|
|
)
|
|
|
|
result = client.create_upload_and_wait_video(
|
|
CreateVideoRequest(title="Demo", size=4, classification=["ITEM"]),
|
|
content=b"demo",
|
|
poll_interval_seconds=0.0,
|
|
)
|
|
|
|
assert isinstance(result, VideoWorkflowResult)
|
|
assert result.video_id == "VIDEO-9"
|
|
assert result.video.videoId == "VIDEO-9"
|
|
assert result.created.resource_id == "VIDEO-9"
|
|
assert calls[0][0] == "create_video"
|
|
assert calls[1] == (
|
|
"upload_video",
|
|
{"video_id": "VIDEO-9", "content": b"demo", "content_length": 4, "content_range": None},
|
|
)
|
|
assert calls[2] == (
|
|
"wait_for_video",
|
|
{"video_id": "VIDEO-9", "timeout_seconds": 30.0, "poll_interval_seconds": 0.0},
|
|
)
|
|
|
|
|
|
def test_media_create_upload_and_wait_document_orchestrates_flow(monkeypatch) -> None:
|
|
client = MediaClient(build_transport())
|
|
calls: list[tuple[str, object]] = []
|
|
|
|
monkeypatch.setattr(
|
|
client,
|
|
"create_document",
|
|
lambda payload: calls.append(("create_document", payload))
|
|
or CreateDocumentResponse(documentId="DOC-9", documentStatus="PENDING_UPLOAD"),
|
|
)
|
|
monkeypatch.setattr(
|
|
client,
|
|
"upload_document",
|
|
lambda document_id, **kwargs: calls.append(("upload_document", {"document_id": document_id, **kwargs}))
|
|
or DocumentResponse(documentId=document_id, documentStatus="SUBMITTED"),
|
|
)
|
|
monkeypatch.setattr(
|
|
client,
|
|
"wait_for_document",
|
|
lambda document_id, **kwargs: calls.append(("wait_for_document", {"document_id": document_id, **kwargs}))
|
|
or DocumentResponse(documentId=document_id, documentStatus="ACCEPTED"),
|
|
)
|
|
|
|
result = client.create_upload_and_wait_document(
|
|
CreateDocumentRequest(documentType="USER_GUIDE_OR_MANUAL", languages=["en-US"]),
|
|
file_name="guide.pdf",
|
|
content=b"%PDF-1.7",
|
|
content_type="application/pdf",
|
|
poll_interval_seconds=0.0,
|
|
)
|
|
|
|
assert isinstance(result, DocumentWorkflowResult)
|
|
assert result.document_id == "DOC-9"
|
|
assert result.created.documentId == "DOC-9"
|
|
assert result.uploaded is not None and result.uploaded.documentStatus == "SUBMITTED"
|
|
assert result.document.documentStatus == "ACCEPTED"
|
|
assert calls[0][0] == "create_document"
|
|
assert calls[1] == (
|
|
"upload_document",
|
|
{
|
|
"document_id": "DOC-9",
|
|
"file_name": "guide.pdf",
|
|
"content": b"%PDF-1.7",
|
|
"content_type": "application/pdf",
|
|
},
|
|
)
|
|
assert calls[2] == (
|
|
"wait_for_document",
|
|
{"document_id": "DOC-9", "timeout_seconds": 30.0, "poll_interval_seconds": 0.0},
|
|
)
|
|
|
|
|
|
def test_media_create_document_from_url_and_wait_orchestrates_flow(monkeypatch) -> None:
|
|
client = MediaClient(build_transport())
|
|
calls: list[tuple[str, object]] = []
|
|
|
|
monkeypatch.setattr(
|
|
client,
|
|
"create_document_from_url",
|
|
lambda payload: calls.append(("create_document_from_url", payload))
|
|
or CreateDocumentResponse(documentId="DOC-10", documentStatus="SUBMITTED"),
|
|
)
|
|
monkeypatch.setattr(
|
|
client,
|
|
"wait_for_document",
|
|
lambda document_id, **kwargs: calls.append(("wait_for_document", {"document_id": document_id, **kwargs}))
|
|
or DocumentResponse(documentId=document_id, documentStatus="ACCEPTED"),
|
|
)
|
|
|
|
result = client.create_document_from_url_and_wait(
|
|
CreateDocumentFromUrlRequest(
|
|
documentType="USER_GUIDE_OR_MANUAL",
|
|
documentUrl="https://example.test/guide.pdf",
|
|
languages=["en-US"],
|
|
),
|
|
poll_interval_seconds=0.0,
|
|
)
|
|
|
|
assert isinstance(result, DocumentWorkflowResult)
|
|
assert result.document_id == "DOC-10"
|
|
assert result.created.documentId == "DOC-10"
|
|
assert result.uploaded is None
|
|
assert result.document.documentStatus == "ACCEPTED"
|
|
assert calls[0][0] == "create_document_from_url"
|
|
assert calls[1] == (
|
|
"wait_for_document",
|
|
{"document_id": "DOC-10", "timeout_seconds": 30.0, "poll_interval_seconds": 0.0},
|
|
)
|
|
|
|
|
|
def test_media_convenience_methods_raise_when_required_ids_are_missing(monkeypatch) -> None:
|
|
client = MediaClient(build_transport())
|
|
|
|
monkeypatch.setattr(client, "create_video", lambda payload: CreatedMediaResource(resource_id=None))
|
|
monkeypatch.setattr(client, "create_document", lambda payload: CreateDocumentResponse(documentId=None))
|
|
monkeypatch.setattr(client, "create_document_from_url", lambda payload: CreateDocumentResponse(documentId=None))
|
|
|
|
for action in (
|
|
lambda: client.create_upload_and_wait_video(
|
|
CreateVideoRequest(title="Demo", size=4, classification=["ITEM"]),
|
|
content=b"demo",
|
|
poll_interval_seconds=0.0,
|
|
),
|
|
lambda: client.create_upload_and_wait_document(
|
|
CreateDocumentRequest(documentType="USER_GUIDE_OR_MANUAL", languages=["en-US"]),
|
|
file_name="guide.pdf",
|
|
content=b"%PDF-1.7",
|
|
poll_interval_seconds=0.0,
|
|
),
|
|
lambda: client.create_document_from_url_and_wait(
|
|
CreateDocumentFromUrlRequest(
|
|
documentType="USER_GUIDE_OR_MANUAL",
|
|
documentUrl="https://example.test/guide.pdf",
|
|
languages=["en-US"],
|
|
),
|
|
poll_interval_seconds=0.0,
|
|
),
|
|
):
|
|
try:
|
|
action()
|
|
except RuntimeError:
|
|
pass
|
|
else:
|
|
raise AssertionError("Expected convenience method to raise when eBay omits the required identifier")
|
|
|
|
|
|
def test_guess_media_content_type_uses_filename_extension() -> None:
|
|
assert guess_media_content_type("photo.jpg") == "image/jpeg"
|
|
assert guess_media_content_type("guide.pdf") == "application/pdf"
|
|
assert guess_media_content_type("unknown.custom") == "application/octet-stream"
|
|
|
|
|
|
def test_media_create_image_from_path_reads_file_and_infers_content_type(tmp_path, monkeypatch) -> None:
|
|
client = MediaClient(build_transport())
|
|
image_path = tmp_path / "photo.png"
|
|
image_path.write_bytes(b"png-data")
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
monkeypatch.setattr(
|
|
client,
|
|
"create_image_from_file",
|
|
lambda **kwargs: captured.update(kwargs) or ImageResponse(imageUrl="https://example.test/image"),
|
|
)
|
|
|
|
result = client.create_image_from_path(image_path)
|
|
|
|
assert result.imageUrl == "https://example.test/image"
|
|
assert captured == {
|
|
"file_name": "photo.png",
|
|
"content": b"png-data",
|
|
"content_type": "image/png",
|
|
}
|
|
|
|
|
|
def test_media_upload_document_from_path_reads_file_and_infers_content_type(tmp_path, monkeypatch) -> None:
|
|
client = MediaClient(build_transport())
|
|
document_path = tmp_path / "guide.pdf"
|
|
document_path.write_bytes(b"%PDF-1.7")
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
monkeypatch.setattr(
|
|
client,
|
|
"upload_document",
|
|
lambda document_id, **kwargs: captured.update({"document_id": document_id, **kwargs})
|
|
or DocumentResponse(documentId=document_id, documentStatus="SUBMITTED"),
|
|
)
|
|
|
|
result = client.upload_document_from_path("DOC-42", document_path)
|
|
|
|
assert result.documentId == "DOC-42"
|
|
assert captured == {
|
|
"document_id": "DOC-42",
|
|
"file_name": "guide.pdf",
|
|
"content": b"%PDF-1.7",
|
|
"content_type": "application/pdf",
|
|
}
|
|
|
|
|
|
def test_media_create_upload_and_wait_document_from_path_reads_file_and_delegates(tmp_path, monkeypatch) -> None:
|
|
client = MediaClient(build_transport())
|
|
document_path = tmp_path / "guide.pdf"
|
|
document_path.write_bytes(b"%PDF-1.7")
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
monkeypatch.setattr(
|
|
client,
|
|
"create_upload_and_wait_document",
|
|
lambda payload, **kwargs: captured.update({"payload": payload, **kwargs})
|
|
or DocumentWorkflowResult(
|
|
created=CreateDocumentResponse(documentId="DOC-77", documentStatus="PENDING_UPLOAD"),
|
|
uploaded=DocumentResponse(documentId="DOC-77", documentStatus="SUBMITTED"),
|
|
document=DocumentResponse(documentId="DOC-77", documentStatus="ACCEPTED"),
|
|
document_id="DOC-77",
|
|
),
|
|
)
|
|
|
|
result = client.create_upload_and_wait_document_from_path(
|
|
CreateDocumentRequest(documentType="USER_GUIDE_OR_MANUAL", languages=["en-US"]),
|
|
document_path,
|
|
poll_interval_seconds=0.0,
|
|
)
|
|
|
|
assert isinstance(result, DocumentWorkflowResult)
|
|
assert result.document.documentStatus == "ACCEPTED"
|
|
assert captured["file_name"] == "guide.pdf"
|
|
assert captured["content"] == b"%PDF-1.7"
|
|
assert captured["content_type"] == "application/pdf"
|
|
assert captured["poll_interval_seconds"] == 0.0
|
|
|
|
|
|
def test_media_create_upload_and_wait_video_from_path_builds_payload_and_delegates(tmp_path, monkeypatch) -> None:
|
|
client = MediaClient(build_transport())
|
|
video_path = tmp_path / "demo.mp4"
|
|
video_path.write_bytes(b"video-data")
|
|
|
|
captured: dict[str, object] = {}
|
|
|
|
monkeypatch.setattr(
|
|
client,
|
|
"create_upload_and_wait_video",
|
|
lambda payload, **kwargs: captured.update({"payload": payload, **kwargs})
|
|
or VideoWorkflowResult(
|
|
created=CreatedMediaResource(resource_id="VIDEO-88"),
|
|
video=Video(videoId="VIDEO-88", status="LIVE"),
|
|
video_id="VIDEO-88",
|
|
),
|
|
)
|
|
|
|
result = client.create_upload_and_wait_video_from_path(
|
|
video_path,
|
|
description="demo video",
|
|
poll_interval_seconds=0.0,
|
|
)
|
|
|
|
assert isinstance(result, VideoWorkflowResult)
|
|
assert result.video.videoId == "VIDEO-88"
|
|
payload = captured["payload"]
|
|
assert isinstance(payload, CreateVideoRequest)
|
|
assert payload.title == "demo"
|
|
assert payload.size == len(b"video-data")
|
|
assert payload.classification == ["ITEM"]
|
|
assert payload.description == "demo video"
|
|
assert captured["content"] == b"video-data"
|
|
assert captured["poll_interval_seconds"] == 0.0 |