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 FeedClient from ebay_client.fulfillment.client import 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.fulfillment.models import Order from ebay_client.generated.inventory.models import InventoryItemWithSkuLocaleGroupid 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_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_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_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