from unittest import mock import httpx import pytest from app.enums import AdReportingPlatform from app.models import ( AdReportingCreativeThumbnailUrl, Creative, ) from app.processors import ( GoogleCreativeProcessor, ) @pytest.mark.parametrize( "source_url", [ "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "https://www.youtu.be/dQw4w9WgXcQ", "https://www.youtube.com/shorts/dQw4w9WgXcQ", ], ) @pytest.mark.asyncio @mock.patch("app.image.uuid4", return_value="test_uuid") async def test_google_creative_processor_youtube_link( s3_client_mock: mock.AsyncMock, web_client_mock: mock.AsyncMock, assets_s3_bucket_name: str, assets_s3_raw_path: str, assets_s3_thumbnail_path: str, source_url: str, ) -> None: processor = GoogleCreativeProcessor( s3_client=s3_client_mock, web_client=web_client_mock, assets_s3_bucket_name=assets_s3_bucket_name, assets_s3_raw_path=assets_s3_raw_path, assets_s3_thumbnail_path=assets_s3_thumbnail_path, ) creative = Creative( platform=AdReportingPlatform.GOOGLE, creative_id="test", source_url=source_url, fails_count=0, ) s3_client_mock.put_object = mock.AsyncMock() result = await processor.process(creative) assert isinstance(result, AdReportingCreativeThumbnailUrl) assert result.platform == creative.platform assert result.creative_id == creative.creative_id assert result.source_url == creative.source_url assert result.img_s3_obj_key == f"{assets_s3_raw_path}/test_uuid.png" assert result.img_s3_obj_key == f"{assets_s3_thumbnail_path}/test_uuid.png" web_client_mock.get.assert_called_once_with( GoogleCreativeProcessor.YOUTUBE_IMAGE_URL.format( video_id="dQw4w9WgXcQ", resolution_prefix="maxres" ), ) s3_client_mock.put_object.assert_has_calls( ( mock.call( assets_s3_bucket_name, f"{assets_s3_raw_path}/test_uuid.png", mock.ANY, "image/png", ), mock.call( assets_s3_bucket_name, f"{assets_s3_thumbnail_path}/test_uuid.png", mock.ANY, "image/png", ), ), ) @pytest.mark.asyncio @mock.patch("app.image.uuid4", return_value="test_uuid") async def test_google_creative_processor_googlesyndicate_link( s3_client_mock: mock.AsyncMock, web_client_mock: mock.AsyncMock, assets_s3_bucket_name: str, assets_s3_raw_path: str, assets_s3_thumbnail_path: str, ) -> None: processor = GoogleCreativeProcessor( s3_client=s3_client_mock, web_client=web_client_mock, assets_s3_bucket_name=assets_s3_bucket_name, assets_s3_raw_path=assets_s3_raw_path, assets_s3_thumbnail_path=assets_s3_thumbnail_path, ) creative = Creative( platform=AdReportingPlatform.GOOGLE, creative_id="test", source_url="https://test.googlesyndication.com/test/111", fails_count=0, ) s3_client_mock.put_object = mock.AsyncMock() result = await processor.process(creative) assert isinstance(result, AdReportingCreativeThumbnailUrl) assert result.platform == creative.platform assert result.creative_id == creative.creative_id assert result.source_url == creative.source_url assert result.img_s3_obj_key == f"{assets_s3_raw_path}/test_uuid.png" assert result.img_s3_obj_key == f"{assets_s3_thumbnail_path}/test_uuid.png" web_client_mock.get.assert_called_once_with(creative.source_url) s3_client_mock.put_object.assert_has_calls( ( mock.call( assets_s3_bucket_name, f"{assets_s3_raw_path}/test_uuid.png", mock.ANY, "image/png", ), mock.call( assets_s3_bucket_name, f"{assets_s3_thumbnail_path}/test_uuid.png", mock.ANY, "image/png", ), ), ) @pytest.mark.asyncio @mock.patch("app.image.uuid4", return_value="test_uuid") async def test_google_creative_processor_lnkto_link( s3_client_mock: mock.AsyncMock, web_client_mock: mock.AsyncMock, assets_s3_bucket_name: str, assets_s3_raw_path: str, assets_s3_thumbnail_path: str, web_response_mock: httpx.Response, ) -> None: processor = GoogleCreativeProcessor( s3_client=s3_client_mock, web_client=web_client_mock, assets_s3_bucket_name=assets_s3_bucket_name, assets_s3_raw_path=assets_s3_raw_path, assets_s3_thumbnail_path=assets_s3_thumbnail_path, ) creative = Creative( platform=AdReportingPlatform.GOOGLE, creative_id="test", source_url="https://lnk.to/test", fails_count=0, ) s3_client_mock.put_object = mock.AsyncMock() lnkto_response_mock = mock.MagicMock() lnkto_response_mock.content = ( rb"test_page =https:\/\/www.youtube.com\/watch?v=dQw4w9WgXcQ= test_page " ) web_client_mock.get.side_effect = (lnkto_response_mock, web_response_mock) result = await processor.process(creative) assert isinstance(result, AdReportingCreativeThumbnailUrl) assert result.platform == creative.platform assert result.creative_id == creative.creative_id assert result.source_url == creative.source_url assert result.img_s3_obj_key == f"{assets_s3_raw_path}/test_uuid.png" assert result.img_s3_obj_key == f"{assets_s3_thumbnail_path}/test_uuid.png" web_client_mock.get.assert_has_calls( ( mock.call( creative.source_url, ), mock.call( GoogleCreativeProcessor.YOUTUBE_IMAGE_URL.format( video_id="dQw4w9WgXcQ", resolution_prefix="maxres" ) ), ) ) s3_client_mock.put_object.assert_has_calls( ( mock.call( assets_s3_bucket_name, f"{assets_s3_raw_path}/test_uuid.png", mock.ANY, "image/png", ), mock.call( assets_s3_bucket_name, f"{assets_s3_thumbnail_path}/test_uuid.png", mock.ANY, "image/png", ), ), ) @pytest.mark.asyncio @mock.patch("app.image.uuid4", return_value="test_uuid") async def test_google_creative_processor_spotify_link( s3_client_mock: mock.AsyncMock, web_client_mock: mock.AsyncMock, assets_s3_bucket_name: str, assets_s3_raw_path: str, assets_s3_thumbnail_path: str, web_response_mock: httpx.Response, ) -> None: processor = GoogleCreativeProcessor( s3_client=s3_client_mock, web_client=web_client_mock, assets_s3_bucket_name=assets_s3_bucket_name, assets_s3_raw_path=assets_s3_raw_path, assets_s3_thumbnail_path=assets_s3_thumbnail_path, ) creative = Creative( platform=AdReportingPlatform.GOOGLE, creative_id="test", source_url="https://open.spotify.com/playlist/test", fails_count=0, ) s3_client_mock.put_object = mock.AsyncMock() lnkto_response_mock = mock.MagicMock() lnkto_response_mock.json.return_value = {"thumbnail_url": "test_url"} web_client_mock.get.side_effect = (lnkto_response_mock, web_response_mock) result = await processor.process(creative) assert isinstance(result, AdReportingCreativeThumbnailUrl) assert result.platform == creative.platform assert result.creative_id == creative.creative_id assert result.source_url == creative.source_url assert result.img_s3_obj_key == f"{assets_s3_raw_path}/test_uuid.png" assert result.img_s3_obj_key == f"{assets_s3_thumbnail_path}/test_uuid.png" web_client_mock.get.assert_has_calls( ( mock.call( processor.SPOTIFY_EMBEDDED_URL, params={"url": creative.source_url}, ), mock.call("test_url"), ) ) s3_client_mock.put_object.assert_has_calls( ( mock.call( assets_s3_bucket_name, f"{assets_s3_raw_path}/test_uuid.png", mock.ANY, "image/png", ), mock.call( assets_s3_bucket_name, f"{assets_s3_thumbnail_path}/test_uuid.png", mock.ANY, "image/png", ), ), ) @pytest.mark.asyncio @mock.patch("app.image.uuid4", return_value="test_uuid") async def test_google_creative_processor_lnkto_link_maxres_not_available( s3_client_mock: mock.AsyncMock, web_client_mock: mock.AsyncMock, assets_s3_bucket_name: str, assets_s3_raw_path: str, assets_s3_thumbnail_path: str, web_response_mock: httpx.Response, not_found_response_mock: httpx.Response, ) -> None: processor = GoogleCreativeProcessor( s3_client=s3_client_mock, web_client=web_client_mock, assets_s3_bucket_name=assets_s3_bucket_name, assets_s3_raw_path=assets_s3_raw_path, assets_s3_thumbnail_path=assets_s3_thumbnail_path, ) creative = Creative( platform=AdReportingPlatform.GOOGLE, creative_id="test", source_url="https://lnk.to/test", fails_count=0, ) s3_client_mock.put_object = mock.AsyncMock() lnkto_response_mock = mock.MagicMock() lnkto_response_mock.content = ( rb"test_page =https:\/\/www.youtube.com\/watch?v=dQw4w9WgXcQ= test_page " ) web_client_mock.get.side_effect = ( lnkto_response_mock, not_found_response_mock, web_response_mock, ) result = await processor.process(creative) assert isinstance(result, AdReportingCreativeThumbnailUrl) assert result.platform == creative.platform assert result.creative_id == creative.creative_id assert result.source_url == creative.source_url assert result.img_s3_obj_key == f"{assets_s3_raw_path}/test_uuid.png" assert result.img_s3_obj_key == f"{assets_s3_thumbnail_path}/test_uuid.png" web_client_mock.get.assert_has_calls( ( mock.call( creative.source_url, ), mock.call( GoogleCreativeProcessor.YOUTUBE_IMAGE_URL.format( video_id="dQw4w9WgXcQ", resolution_prefix="maxres" ) ), mock.call( GoogleCreativeProcessor.YOUTUBE_IMAGE_URL.format( video_id="dQw4w9WgXcQ", resolution_prefix="hq" ) ), ) ) s3_client_mock.put_object.assert_has_calls( ( mock.call( assets_s3_bucket_name, f"{assets_s3_raw_path}/test_uuid.png", mock.ANY, "image/png", ), mock.call( assets_s3_bucket_name, f"{assets_s3_thumbnail_path}/test_uuid.png", mock.ANY, "image/png", ), ), ) @pytest.mark.asyncio @mock.patch("app.image.uuid4", return_value="test_uuid") async def test_google_creative_processor_random_link( s3_client_mock: mock.AsyncMock, web_client_mock: mock.AsyncMock, assets_s3_bucket_name: str, assets_s3_raw_path: str, assets_s3_thumbnail_path: str, ) -> None: processor = GoogleCreativeProcessor( s3_client=s3_client_mock, web_client=web_client_mock, assets_s3_bucket_name=assets_s3_bucket_name, assets_s3_raw_path=assets_s3_raw_path, assets_s3_thumbnail_path=assets_s3_thumbnail_path, ) creative = Creative( platform=AdReportingPlatform.GOOGLE, creative_id="test", source_url="test", fails_count=0, ) s3_client_mock.put_object = mock.AsyncMock() result = await processor.process(creative) assert isinstance(result, AdReportingCreativeThumbnailUrl) assert result.platform == creative.platform assert result.creative_id == creative.creative_id assert result.source_url == creative.source_url assert result.img_s3_obj_key == f"{assets_s3_raw_path}/test_uuid.png" assert result.img_s3_obj_key == f"{assets_s3_thumbnail_path}/test_uuid.png" web_client_mock.get.assert_called_once_with(creative.source_url) s3_client_mock.put_object.assert_has_calls( ( mock.call( assets_s3_bucket_name, f"{assets_s3_raw_path}/test_uuid.png", mock.ANY, "image/png", ), mock.call( assets_s3_bucket_name, f"{assets_s3_thumbnail_path}/test_uuid.png", mock.ANY, "image/png", ), ), ) @pytest.mark.asyncio @mock.patch("app.image.uuid4", return_value="test_uuid") async def test_google_creative_processor_random_link_non_image( s3_client_mock: mock.AsyncMock, web_client_mock: mock.AsyncMock, assets_s3_bucket_name: str, assets_s3_raw_path: str, assets_s3_thumbnail_path: str, ) -> None: processor = GoogleCreativeProcessor( s3_client=s3_client_mock, web_client=web_client_mock, assets_s3_bucket_name=assets_s3_bucket_name, assets_s3_raw_path=assets_s3_raw_path, assets_s3_thumbnail_path=assets_s3_thumbnail_path, ) creative = Creative( platform=AdReportingPlatform.GOOGLE, creative_id="test", source_url="test", fails_count=0, ) s3_client_mock.put_object = mock.AsyncMock() non_image_web_response_mock = mock.MagicMock() non_image_web_response_mock.content = b"non_image_response" web_client_mock.get.side_effect = non_image_web_response_mock result = await processor.process(creative) assert isinstance(result, AdReportingCreativeThumbnailUrl) assert result.platform == creative.platform assert result.creative_id == creative.creative_id assert result.source_url == creative.source_url assert result.img_s3_obj_key is None assert result.img_s3_obj_key is None assert result.fails_count == 1 web_client_mock.get.assert_called_once_with(creative.source_url) s3_client_mock.put_object.assert_not_called()