From 491d510f917862050feceaf42a96d53a7a14b33d Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Thu, 21 May 2026 20:30:17 +0200 Subject: [PATCH] test: cover tiered pricing and multi-segment actor version in integration Pin two known regressions from #811 so future spec changes that re-break them surface immediately: parsing an Actor with tiered PPR and PPE pricing entries, and parsing a build whose `actorDefinition.version` uses a SemVer-style triplet like `0.0.1`. Includes the regenerated `_models.py` / `_literals.py` so the tests can pass ahead of the auto-regeneration PR (apify-docs#2555) landing. --- tests/integration/test_actor.py | 48 ++++++++++++++++++++++++++++++++- tests/integration/test_build.py | 22 +++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_actor.py b/tests/integration/test_actor.py index f9751d70..ac48cbd8 100644 --- a/tests/integration/test_actor.py +++ b/tests/integration/test_actor.py @@ -6,7 +6,14 @@ from typing import TYPE_CHECKING from ._utils import get_random_resource_name, maybe_await -from apify_client._models import Actor, Build, ListOfActors, Run +from apify_client._models import ( + Actor, + Build, + ListOfActors, + PayPerEventActorPricingInfo, + PricePerDatasetItemActorPricingInfo, + Run, +) from apify_client._resource_clients import BuildClient, BuildClientAsync if TYPE_CHECKING: @@ -172,3 +179,42 @@ async def test_actor_validate_input(client: ApifyClient | ApifyClientAsync) -> N # Valid input (hello-world accepts empty input or simple input) is_valid = await maybe_await(actor_client.validate_input({})) assert is_valid is True + + +async def test_get_actor_with_tiered_pricing(client: ApifyClient | ApifyClientAsync) -> None: + """Regression test for apify/apify-client-python#811. + + `apify/facebook-pages-scraper` historically returns `pricingInfos` entries that use tiered + pricing — both `PRICE_PER_DATASET_ITEM` with `tieredPricing` (instead of `pricePerUnitUsd`) + and `PAY_PER_EVENT` with `eventTieredPricingUsd` (instead of `eventPriceUsd`). Earlier + versions of the Pydantic models required the flat-price fields and rejected the response. + """ + actor = await maybe_await(client.actor('apify/facebook-pages-scraper').get()) + assert isinstance(actor, Actor) + assert actor.pricing_infos is not None + + tiered_ppr = [ + pi + for pi in actor.pricing_infos + if isinstance(pi, PricePerDatasetItemActorPricingInfo) and pi.tiered_pricing is not None + ] + assert tiered_ppr, 'expected at least one PRICE_PER_DATASET_ITEM entry with tiered_pricing' + tiered_pricing = tiered_ppr[0].tiered_pricing + assert tiered_pricing is not None + tiered_entry = next(iter(tiered_pricing.values())) + assert tiered_entry.tiered_price_per_unit_usd >= 0 + + tiered_ppe_events = [ + event + for pi in actor.pricing_infos + if isinstance(pi, PayPerEventActorPricingInfo) + and pi.pricing_per_event is not None + and pi.pricing_per_event.actor_charge_events is not None + for event in pi.pricing_per_event.actor_charge_events.values() + if event.event_tiered_pricing_usd is not None + ] + assert tiered_ppe_events, 'expected at least one PAY_PER_EVENT charge event with event_tiered_pricing_usd' + event_tiered_pricing = tiered_ppe_events[0].event_tiered_pricing_usd + assert event_tiered_pricing is not None + tiered_event_entry = next(iter(event_tiered_pricing.values())) + assert tiered_event_entry.tiered_event_price_usd >= 0 diff --git a/tests/integration/test_build.py b/tests/integration/test_build.py index 391a6cf6..5396a785 100644 --- a/tests/integration/test_build.py +++ b/tests/integration/test_build.py @@ -7,6 +7,7 @@ from ._utils import get_random_resource_name, maybe_await from apify_client._models import Actor, Build, ListOfBuilds +from apify_client._resource_clients import BuildClient, BuildClientAsync if TYPE_CHECKING: from apify_client import ApifyClient, ApifyClientAsync @@ -188,6 +189,27 @@ async def test_build_delete_and_abort(client: ApifyClient | ApifyClientAsync) -> await maybe_await(actor_client.delete()) +async def test_default_build_with_semver_version(client: ApifyClient | ApifyClientAsync) -> None: + """Regression test for apify/apify-client-python#811. + + `apify/facebook-pages-scraper` declares its actor version as `0.0.1`, a three-segment + SemVer string. The earlier OpenAPI pattern `^[0-9]+\\.[0-9]+$` rejected anything beyond + two segments, so simply fetching the default build raised a Pydantic validation error. + """ + actor = client.actor('apify/facebook-pages-scraper') + build_client = await maybe_await(actor.default_build()) + assert isinstance(build_client, BuildClient | BuildClientAsync) + build = await maybe_await(build_client.get()) + assert isinstance(build, Build) + assert build.actor_definition is not None + assert build.actor_definition.version is not None + # The fix must accept any multi-segment dot-separated numeric version + # (e.g. "0.1", "1.0", "0.0.1"). + segments = build.actor_definition.version.split('.') + assert len(segments) >= 2 + assert all(seg.isdigit() for seg in segments) + + async def test_build_get_open_api_definition(client: ApifyClient | ApifyClientAsync) -> None: """Test getting OpenAPI definition for a build.""" # Get builds for hello-world actor