Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,9 @@ jobs:
matrix:
include:
# Core also builds and installs the reporting wheel/sdist fixtures.
# It runs 25-29 minutes on main, so 30 cancelled it on slow runners.
- lane: core
timeout_minutes: 30
timeout_minutes: 45
- lane: process
timeout_minutes: 15
services:
Expand Down
5 changes: 4 additions & 1 deletion src/adcp/types/_forward_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
from pydantic_core import CoreSchema, InitErrorDetails, PydanticCustomError, core_schema
from pydantic_core.core_schema import ErrorType

from adcp.types.base import AdCPBaseModel
from adcp.types.base import AdCPBaseModel, NoExplicitNull
from adcp.types.canonical_creative import CreateMediaBuyRequest as _PublicCreateMediaBuyRequest
from adcp.types.canonical_creative import PackageRequest as PublicPackageRequest
from adcp.types.canonical_creative import PackageUpdate as PublicPackageUpdate
Expand Down Expand Up @@ -156,6 +156,9 @@
"identical wire shape, so confirm seller support before relying on it."
),
),
# Optional, not nullable: a JSON null is refused, as on the generated
# push_notification_config.operation_id this field mirrors (#1547).
NoExplicitNull,
]

if TYPE_CHECKING:
Expand Down
19 changes: 18 additions & 1 deletion tests/test_reporting_webhook_operation_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,12 @@ def test_reporting_operation_id_constraints_match_push_notification_config() ->
assert reporting.annotation == push.annotation
assert [repr(item) for item in reporting.metadata] == [repr(item) for item in push.metadata]
schema = ReportingWebhook.model_json_schema()["properties"]["operation_id"]
assert schema["anyOf"][0] == {
push_schema = PushNotificationConfig.model_json_schema()["properties"]["operation_id"]
wire_keys = ("type", "minLength", "maxLength", "pattern", "anyOf")
assert {key: schema.get(key) for key in wire_keys} == {
key: push_schema.get(key) for key in wire_keys
}
assert {key: schema[key] for key in ("type", "minLength", "maxLength", "pattern")} == {
"type": "string",
"minLength": 1,
"maxLength": 255,
Expand All @@ -229,6 +234,18 @@ def test_reporting_operation_id_constraints_match_push_notification_config() ->
assert "MUST NOT emit `media_buy_delivery`" in schema["description"]


def test_reporting_operation_id_refuses_an_explicit_json_null() -> None:
"""Omitted is fine; a JSON null is not, matching the generated push field."""
base = {
"url": "https://buyer.example/reporting",
"authentication": _AUTHENTICATION,
"reporting_frequency": "daily",
}
assert ReportingWebhook.model_validate_json(json.dumps(base)).operation_id is None
with pytest.raises(ValidationError):
ReportingWebhook.model_validate_json(json.dumps({**base, "operation_id": None}))


@pytest.mark.asyncio
async def test_stored_registration_correlates_delivery_and_retry(tmp_path: Path) -> None:
registration = ReportingWebhook(
Expand Down
Loading