diff --git a/src/creative_agent/data/standard_formats.py b/src/creative_agent/data/standard_formats.py index 476a638..aa74610 100644 --- a/src/creative_agent/data/standard_formats.py +++ b/src/creative_agent/data/standard_formats.py @@ -83,6 +83,23 @@ def create_impression_tracker_asset() -> LibAssets: ) +def create_click_tracker_asset() -> LibAssets: + """Create an optional click tracker asset for 3rd party tracking. + + This creates a URL asset with url_type='tracker_redirect' that can be used + for third-party click tracking redirects. + """ + return create_asset( + asset_id="click_tracker", + asset_type=AssetType.url, + required=False, + requirements={ + "url_type": "tracker_redirect", + "description": "3rd party click tracking redirect URL", + }, + ) + + def create_fixed_render(width: int, height: int, role: str = "primary") -> LibRender: """Create a render with fixed dimensions (non-responsive). @@ -156,6 +173,7 @@ def create_responsive_render( requirements={"description": "Text prompt describing the desired creative"}, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), # Concrete formats for backward compatibility @@ -181,6 +199,7 @@ def create_responsive_render( requirements={"description": "Text prompt describing the desired creative"}, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -205,6 +224,7 @@ def create_responsive_render( requirements={"description": "Text prompt describing the desired creative"}, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -229,6 +249,7 @@ def create_responsive_render( requirements={"description": "Text prompt describing the desired creative"}, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -253,6 +274,7 @@ def create_responsive_render( requirements={"description": "Text prompt describing the desired creative"}, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -277,6 +299,7 @@ def create_responsive_render( requirements={"description": "Text prompt describing the desired creative"}, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -301,6 +324,7 @@ def create_responsive_render( requirements={"description": "Text prompt describing the desired creative"}, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -325,6 +349,7 @@ def create_responsive_render( requirements={"description": "Text prompt describing the desired creative"}, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), ] @@ -386,6 +411,7 @@ def create_responsive_render( asset_type=AssetType.vast, required=True, ), + create_click_tracker_asset(), ], ), # Concrete formats for backward compatibility @@ -444,6 +470,7 @@ def create_responsive_render( "description": "VAST 4.x compatible tag", }, ), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -605,6 +632,7 @@ def create_responsive_render( }, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), # Concrete formats for backward compatibility @@ -636,6 +664,7 @@ def create_responsive_render( }, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -663,6 +692,7 @@ def create_responsive_render( required=True, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -690,6 +720,7 @@ def create_responsive_render( required=True, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -717,6 +748,7 @@ def create_responsive_render( required=True, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -744,6 +776,7 @@ def create_responsive_render( required=True, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -771,6 +804,7 @@ def create_responsive_render( required=True, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -798,6 +832,7 @@ def create_responsive_render( required=True, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), ] @@ -823,6 +858,7 @@ def create_responsive_render( }, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), # Concrete formats for backward compatibility @@ -846,6 +882,7 @@ def create_responsive_render( }, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -867,6 +904,7 @@ def create_responsive_render( }, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -888,6 +926,7 @@ def create_responsive_render( }, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -909,6 +948,7 @@ def create_responsive_render( }, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -930,6 +970,7 @@ def create_responsive_render( }, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), CreativeFormat( @@ -951,6 +992,7 @@ def create_responsive_render( }, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), ] @@ -973,6 +1015,7 @@ def create_responsive_render( required=True, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), ] @@ -1093,6 +1136,7 @@ def create_responsive_render( }, ), create_impression_tracker_asset(), + create_click_tracker_asset(), ], ), ] diff --git a/tests/unit/test_click_tracker.py b/tests/unit/test_click_tracker.py new file mode 100644 index 0000000..daaa5f1 --- /dev/null +++ b/tests/unit/test_click_tracker.py @@ -0,0 +1,113 @@ +"""Unit tests for click_tracker asset functionality.""" + +from creative_agent.data.standard_formats import ( + STANDARD_FORMATS, + create_click_tracker_asset, +) + +# Expected formats that should have click_tracker +EXPECTED_FORMATS_WITH_CLICK_TRACKER = [ + # Generative formats + "display_generative", + "display_300x250_generative", + "display_728x90_generative", + "display_320x50_generative", + "display_160x600_generative", + "display_336x280_generative", + "display_300x600_generative", + "display_970x250_generative", + # Image formats + "display_image", + "display_300x250_image", + "display_728x90_image", + "display_320x50_image", + "display_160x600_image", + "display_336x280_image", + "display_300x600_image", + "display_970x250_image", + # HTML formats + "display_html", + "display_300x250_html", + "display_728x90_html", + "display_160x600_html", + "display_336x280_html", + "display_300x600_html", + "display_970x250_html", + # JS format + "display_js", + # VAST formats + "video_vast", + "video_vast_30s", + # Native + "native_content", +] + + +class TestClickTrackerAsset: + """Tests for the click_tracker asset helper function.""" + + def test_click_tracker_asset_id(self): + """Test click_tracker has correct asset_id.""" + tracker = create_click_tracker_asset() + assert tracker.asset_id == "click_tracker" + + def test_click_tracker_asset_type(self): + """Test click_tracker is a URL type asset.""" + tracker = create_click_tracker_asset() + assert tracker.asset_type.value == "url" + + def test_click_tracker_is_optional(self): + """Test click_tracker is not required.""" + tracker = create_click_tracker_asset() + assert tracker.required is False + + def test_click_tracker_url_type(self): + """Test click_tracker uses tracker_redirect url_type.""" + tracker = create_click_tracker_asset() + assert tracker.requirements["url_type"] == "tracker_redirect" + + def test_click_tracker_has_description(self): + """Test click_tracker has a description in requirements.""" + tracker = create_click_tracker_asset() + assert "description" in tracker.requirements + assert "click" in tracker.requirements["description"].lower() + + +class TestClickTrackerFormatCoverage: + """Tests for click_tracker coverage across formats.""" + + def test_click_tracker_format_count(self): + """Verify exactly 27 formats have click_tracker.""" + count = sum(1 for fmt in STANDARD_FORMATS if "click_tracker" in [a.asset_id for a in (fmt.assets or [])]) + assert count == 27 + + def test_click_tracker_on_expected_formats(self): + """Verify click_tracker is on all expected formats.""" + for fmt in STANDARD_FORMATS: + asset_ids = [a.asset_id for a in (fmt.assets or [])] + if fmt.format_id.id in EXPECTED_FORMATS_WITH_CLICK_TRACKER: + assert "click_tracker" in asset_ids, f"{fmt.format_id.id} should have click_tracker" + + def test_click_tracker_only_on_expected_formats(self): + """Verify click_tracker is only on formats in the expected list.""" + for fmt in STANDARD_FORMATS: + asset_ids = [a.asset_id for a in (fmt.assets or [])] + if "click_tracker" in asset_ids: + assert fmt.format_id.id in EXPECTED_FORMATS_WITH_CLICK_TRACKER, ( + f"{fmt.format_id.id} has click_tracker but is not in expected list" + ) + + def test_click_tracker_not_on_non_clickable_formats(self): + """Verify click_tracker is NOT on formats without click functionality.""" + # Audio and DOOH don't support click tracking + # VAST video formats DO support click tracking (external to VAST tag) + non_clickable_types = ["audio", "dooh"] + for fmt in STANDARD_FORMATS: + fmt_type = str(fmt.type).lower() if fmt.type else "" + asset_ids = [a.asset_id for a in (fmt.assets or [])] + + # Skip if not a non-clickable type + if not any(t in fmt_type for t in non_clickable_types): + continue + + assert "click_tracker" not in asset_ids, f"{fmt.format_id.id} ({fmt_type}) should not have click_tracker"