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
1 change: 1 addition & 0 deletions src/core/schema_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ class ListAuthorizedPropertiesResponse(AdCPBaseModel):
model_config = {"arbitrary_types_allowed": True}

# Fields from AdCP spec v2.4
# Per /schemas/v1/media-buy/list-authorized-properties-response.json
publisher_domains: list[str] = Field(
..., description="Publisher domains this agent is authorized to represent", min_length=1
)
Expand Down
3 changes: 0 additions & 3 deletions src/core/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -3412,9 +3412,6 @@ class ListAuthorizedPropertiesResponse(AdCPBaseModel):
"""

publisher_domains: list[str] = Field(..., description="Publisher domains this agent is authorized to represent")
tags: dict[str, PropertyTagMetadata] = Field(
default_factory=dict, description="Metadata for each tag referenced by properties"
)
primary_channels: list[str] | None = Field(
None, description="Primary advertising channels in this portfolio (helps buyers filter relevance)"
)
Expand Down
14 changes: 9 additions & 5 deletions tests/unit/test_a2a_response_attribute_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,18 @@ def test_list_creative_formats_response_attribute_access(self):
assert isinstance(response.formats, list)

def test_list_authorized_properties_response_attribute_access(self):
"""Verify ListAuthorizedPropertiesResponse has expected flat structure."""
response = ListAuthorizedPropertiesResponse(publisher_domains=["example.com"], tags={})
"""Verify ListAuthorizedPropertiesResponse has expected flat structure per AdCP spec."""
# Per /schemas/v1/media-buy/list-authorized-properties-response.json
response = ListAuthorizedPropertiesResponse(
publisher_domains=["example.com"],
primary_channels=["display"],
)

# Verify expected attributes exist
# Verify expected attributes exist (per AdCP v2.4 spec)
assert hasattr(response, "publisher_domains")
assert hasattr(response, "tags")
assert hasattr(response, "primary_channels")
assert isinstance(response.publisher_domains, list)
assert isinstance(response.tags, dict)
assert isinstance(response.primary_channels, list)

def test_a2a_list_creatives_handler_attribute_extraction(self):
"""Verify A2A handler can extract attributes correctly from response.
Expand Down
11 changes: 4 additions & 7 deletions tests/unit/test_adcp_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -1929,11 +1929,12 @@ def test_list_authorized_properties_request_adcp_compliance(self):
def test_list_authorized_properties_response_adcp_compliance(self):
"""Test that ListAuthorizedPropertiesResponse complies with AdCP v2.4 list-authorized-properties-response schema."""
# Create response with required fields only (per AdCP spec, optional fields should be omitted if not set)
tag_metadata = PropertyTagMetadata(name="Premium Content", description="Premium content tag")
# Per /schemas/v1/media-buy/list-authorized-properties-response.json, only these fields are spec-compliant:
# - publisher_domains (required)
# - primary_channels, primary_countries, portfolio_description, advertising_policies, last_updated, errors (optional)
response = ListAuthorizedPropertiesResponse(
publisher_domains=["example.com"],
tags={"premium_content": tag_metadata},
# errors omitted - per AdCP spec, optional fields with None/empty values should be omitted
# All optional fields omitted - per AdCP spec, optional fields with None/empty values should be omitted
)

# Test AdCP-compliant response
Expand All @@ -1948,10 +1949,6 @@ def test_list_authorized_properties_response_adcp_compliance(self):
# Verify publisher_domains is array
assert isinstance(adcp_response["publisher_domains"], list)

# Verify tags is object when present
assert "tags" in adcp_response, "tags was set, should be present"
assert isinstance(adcp_response["tags"], dict)

# Verify optional fields with None values are omitted per AdCP spec
assert "errors" not in adcp_response, "errors with None/empty value should be omitted"
assert "primary_channels" not in adcp_response, "primary_channels with None value should be omitted"
Expand Down
20 changes: 11 additions & 9 deletions tests/unit/test_authorized_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,20 +172,22 @@ def test_response_with_minimal_fields(self):
response = ListAuthorizedPropertiesResponse(publisher_domains=["example.com"])

assert response.publisher_domains == ["example.com"]
assert response.tags == {}
assert response.errors is None

def test_response_with_all_fields(self):
"""Test response with all fields (per AdCP v2.4 spec)."""
tag_metadata = PropertyTagMetadata(name="Premium Content", description="Premium content tag")
"""Test response with all optional fields (per AdCP v2.4 spec)."""
response = ListAuthorizedPropertiesResponse(
publisher_domains=["example.com"],
tags={"premium_content": tag_metadata},
primary_channels=["display", "video"],
primary_countries=["US", "GB"],
portfolio_description="Premium content portfolio",
advertising_policies="No tobacco or alcohol ads",
last_updated="2025-10-27T12:00:00Z",
errors=[{"code": "WARNING", "message": "Test warning"}],
)

assert len(response.publisher_domains) == 1
assert "premium_content" in response.tags
assert response.primary_channels == ["display", "video"]
assert len(response.errors) == 1

def test_response_model_dump_omits_none_values(self):
Expand All @@ -201,10 +203,10 @@ def test_response_model_dump_omits_none_values(self):
def test_response_adcp_compliance(self):
"""Test that ListAuthorizedPropertiesResponse complies with AdCP v2.4 schema."""
# Create response with required fields only (no optional fields set)
# Per /schemas/v1/media-buy/list-authorized-properties-response.json
response = ListAuthorizedPropertiesResponse(
publisher_domains=["example.com"],
tags={"test": PropertyTagMetadata(name="Test", description="Test tag")},
# errors not set - should be omitted per AdCP spec
# All optional fields omitted - should be excluded from model_dump per AdCP spec
)

# Test AdCP-compliant response
Expand All @@ -224,8 +226,8 @@ def test_response_adcp_compliance(self):
assert "advertising_policies" not in adcp_response, "advertising_policies with None value should be omitted"
assert "last_updated" not in adcp_response, "last_updated with None value should be omitted"

# Verify field count (only publisher_domains and tags should be present)
assert len(adcp_response) == 2, f"Expected 2 fields, got {len(adcp_response)}: {list(adcp_response.keys())}"
# Verify field count (only publisher_domains should be present)
assert len(adcp_response) == 1, f"Expected 1 field, got {len(adcp_response)}: {list(adcp_response.keys())}"

# Test with optional fields explicitly set to non-None values
response_with_optionals = ListAuthorizedPropertiesResponse(
Expand Down