test_mcp_tools_google_ads_analysis.py
1 """Google Ads MCP分析・監視ツール ハンドラーテスト 2 3 パフォーマンス分析、予算分析、オークション分析、RSA分析、 4 BtoB最適化、クリエイティブ、監視、キャプチャ、および追加の 5 キャンペーン/予算/キーワード/広告ハンドラーの検証。 6 """ 7 8 from __future__ import annotations 9 10 import json 11 from unittest.mock import AsyncMock, MagicMock, patch 12 13 import pytest 14 15 16 def _import_google_ads_tools(): 17 from mureo.mcp import tools_google_ads 18 19 return tools_google_ads 20 21 22 def _import_handlers(): 23 from mureo.mcp import _handlers_google_ads 24 25 return _handlers_google_ads 26 27 28 def _mock_google_ads_context(): 29 """Google Ads認証情報とクライアントのモックを返す""" 30 mock_client = AsyncMock() 31 mock_creds = MagicMock() 32 return mock_creds, mock_client 33 34 35 # --------------------------------------------------------------------------- 36 # ハンドラーテスト — キャンペーン・予算(追加分) 37 # --------------------------------------------------------------------------- 38 39 40 @pytest.mark.unit 41 class TestGoogleAdsBudgetCreateAndAccountsHandlers: 42 """予算作成・アカウント一覧・ネットワーク/広告パフォーマンスレポートのテスト""" 43 44 async def test_budget_create(self) -> None: 45 mod = _import_google_ads_tools() 46 creds, client = _mock_google_ads_context() 47 client.create_budget.return_value = {"resource_name": "res"} 48 49 h = _import_handlers() 50 with ( 51 patch.object(h, "load_google_ads_credentials", return_value=creds), 52 patch.object(h, "create_google_ads_client", return_value=client), 53 ): 54 result = await mod.handle_tool( 55 "google_ads.budget.create", 56 {"customer_id": "123", "name": "Daily Budget", "amount": 5000}, 57 ) 58 59 client.create_budget.assert_awaited_once() 60 parsed = json.loads(result[0].text) 61 assert "resource_name" in parsed 62 63 async def test_accounts_list(self) -> None: 64 mod = _import_google_ads_tools() 65 creds, client = _mock_google_ads_context() 66 client.list_accounts.return_value = [{"id": "111", "name": "Account1"}] 67 68 h = _import_handlers() 69 with ( 70 patch.object(h, "load_google_ads_credentials", return_value=creds), 71 patch.object(h, "create_google_ads_client", return_value=client), 72 ): 73 result = await mod.handle_tool( 74 "google_ads.accounts.list", 75 {"customer_id": "123"}, 76 ) 77 78 client.list_accounts.assert_awaited_once() 79 parsed = json.loads(result[0].text) 80 assert len(parsed) == 1 81 82 async def test_network_performance_report(self) -> None: 83 mod = _import_google_ads_tools() 84 creds, client = _mock_google_ads_context() 85 client.get_network_performance_report.return_value = [{"network": "SEARCH"}] 86 87 h = _import_handlers() 88 with ( 89 patch.object(h, "load_google_ads_credentials", return_value=creds), 90 patch.object(h, "create_google_ads_client", return_value=client), 91 ): 92 result = await mod.handle_tool( 93 "google_ads.network_performance.report", 94 {"customer_id": "123"}, 95 ) 96 97 client.get_network_performance_report.assert_awaited_once() 98 parsed = json.loads(result[0].text) 99 assert parsed[0]["network"] == "SEARCH" 100 101 async def test_ad_performance_report(self) -> None: 102 mod = _import_google_ads_tools() 103 creds, client = _mock_google_ads_context() 104 client.get_ad_performance_report.return_value = [{"ad_id": "1", "clicks": 50}] 105 106 h = _import_handlers() 107 with ( 108 patch.object(h, "load_google_ads_credentials", return_value=creds), 109 patch.object(h, "create_google_ads_client", return_value=client), 110 ): 111 result = await mod.handle_tool( 112 "google_ads.ad_performance.report", 113 {"customer_id": "123"}, 114 ) 115 116 client.get_ad_performance_report.assert_awaited_once() 117 parsed = json.loads(result[0].text) 118 assert parsed[0]["clicks"] == 50 119 120 121 # --------------------------------------------------------------------------- 122 # ハンドラーテスト — キーワード(追加分) 123 # --------------------------------------------------------------------------- 124 125 126 @pytest.mark.unit 127 class TestGoogleAdsKeywordExtendedHandlers: 128 """キーワード追加ハンドラーテスト""" 129 130 async def test_keywords_pause(self) -> None: 131 mod = _import_google_ads_tools() 132 creds, client = _mock_google_ads_context() 133 client.pause_keyword.return_value = {"resource_name": "res"} 134 135 h = _import_handlers() 136 with ( 137 patch.object(h, "load_google_ads_credentials", return_value=creds), 138 patch.object(h, "create_google_ads_client", return_value=client), 139 ): 140 result = await mod.handle_tool( 141 "google_ads.keywords.pause", 142 {"customer_id": "123", "ad_group_id": "10", "criterion_id": "99"}, 143 ) 144 145 client.pause_keyword.assert_awaited_once() 146 parsed = json.loads(result[0].text) 147 assert "resource_name" in parsed 148 149 async def test_negative_keywords_remove(self) -> None: 150 mod = _import_google_ads_tools() 151 creds, client = _mock_google_ads_context() 152 client.remove_negative_keyword.return_value = {"resource_name": "res"} 153 154 h = _import_handlers() 155 with ( 156 patch.object(h, "load_google_ads_credentials", return_value=creds), 157 patch.object(h, "create_google_ads_client", return_value=client), 158 ): 159 result = await mod.handle_tool( 160 "google_ads.negative_keywords.remove", 161 {"customer_id": "123", "campaign_id": "456", "criterion_id": "99"}, 162 ) 163 164 client.remove_negative_keyword.assert_awaited_once() 165 parsed = json.loads(result[0].text) 166 assert "resource_name" in parsed 167 168 async def test_negative_keywords_add_to_ad_group(self) -> None: 169 mod = _import_google_ads_tools() 170 creds, client = _mock_google_ads_context() 171 client.add_negative_keywords_to_ad_group.return_value = [ 172 {"resource_name": "res"} 173 ] 174 175 h = _import_handlers() 176 with ( 177 patch.object(h, "load_google_ads_credentials", return_value=creds), 178 patch.object(h, "create_google_ads_client", return_value=client), 179 ): 180 result = await mod.handle_tool( 181 "google_ads.negative_keywords.add_to_ad_group", 182 { 183 "customer_id": "123", 184 "ad_group_id": "10", 185 "keywords": [{"text": "neg", "match_type": "EXACT"}], 186 }, 187 ) 188 189 client.add_negative_keywords_to_ad_group.assert_awaited_once() 190 parsed = json.loads(result[0].text) 191 assert parsed[0]["resource_name"] == "res" 192 193 async def test_search_terms_analyze(self) -> None: 194 mod = _import_google_ads_tools() 195 creds, client = _mock_google_ads_context() 196 client.analyze_search_terms.return_value = {"categories": []} 197 198 h = _import_handlers() 199 with ( 200 patch.object(h, "load_google_ads_credentials", return_value=creds), 201 patch.object(h, "create_google_ads_client", return_value=client), 202 ): 203 result = await mod.handle_tool( 204 "google_ads.search_terms.analyze", 205 {"customer_id": "123", "campaign_id": "456"}, 206 ) 207 208 client.analyze_search_terms.assert_awaited_once() 209 parsed = json.loads(result[0].text) 210 assert parsed["categories"] == [] 211 212 async def test_negative_keywords_suggest(self) -> None: 213 mod = _import_google_ads_tools() 214 creds, client = _mock_google_ads_context() 215 client.suggest_negative_keywords.return_value = [{"keyword": "free"}] 216 217 h = _import_handlers() 218 with ( 219 patch.object(h, "load_google_ads_credentials", return_value=creds), 220 patch.object(h, "create_google_ads_client", return_value=client), 221 ): 222 result = await mod.handle_tool( 223 "google_ads.negative_keywords.suggest", 224 {"customer_id": "123", "campaign_id": "456"}, 225 ) 226 227 client.suggest_negative_keywords.assert_awaited_once() 228 parsed = json.loads(result[0].text) 229 assert parsed[0]["keyword"] == "free" 230 231 async def test_keywords_audit(self) -> None: 232 mod = _import_google_ads_tools() 233 creds, client = _mock_google_ads_context() 234 client.audit_keywords.return_value = {"issues": [], "score": 85} 235 236 h = _import_handlers() 237 with ( 238 patch.object(h, "load_google_ads_credentials", return_value=creds), 239 patch.object(h, "create_google_ads_client", return_value=client), 240 ): 241 result = await mod.handle_tool( 242 "google_ads.keywords.audit", 243 {"customer_id": "123", "campaign_id": "456"}, 244 ) 245 246 client.audit_keywords.assert_awaited_once() 247 parsed = json.loads(result[0].text) 248 assert parsed["score"] == 85 249 250 async def test_keywords_cross_adgroup_duplicates(self) -> None: 251 mod = _import_google_ads_tools() 252 creds, client = _mock_google_ads_context() 253 client.find_cross_adgroup_duplicates.return_value = {"duplicates": []} 254 255 h = _import_handlers() 256 with ( 257 patch.object(h, "load_google_ads_credentials", return_value=creds), 258 patch.object(h, "create_google_ads_client", return_value=client), 259 ): 260 result = await mod.handle_tool( 261 "google_ads.keywords.cross_adgroup_duplicates", 262 {"customer_id": "123", "campaign_id": "456"}, 263 ) 264 265 client.find_cross_adgroup_duplicates.assert_awaited_once() 266 parsed = json.loads(result[0].text) 267 assert parsed["duplicates"] == [] 268 269 270 # --------------------------------------------------------------------------- 271 # ハンドラーテスト — 広告(追加分) 272 # --------------------------------------------------------------------------- 273 274 275 @pytest.mark.unit 276 class TestGoogleAdsAdExtendedHandlers: 277 """広告追加ハンドラーテスト""" 278 279 async def test_ads_policy_details(self) -> None: 280 mod = _import_google_ads_tools() 281 creds, client = _mock_google_ads_context() 282 client.get_ad_policy_details.return_value = {"policy_topics": []} 283 284 h = _import_handlers() 285 with ( 286 patch.object(h, "load_google_ads_credentials", return_value=creds), 287 patch.object(h, "create_google_ads_client", return_value=client), 288 ): 289 result = await mod.handle_tool( 290 "google_ads.ads.policy_details", 291 {"customer_id": "123", "ad_group_id": "10", "ad_id": "55"}, 292 ) 293 294 client.get_ad_policy_details.assert_awaited_once_with("10", "55") 295 parsed = json.loads(result[0].text) 296 assert parsed["policy_topics"] == [] 297 298 299 # --------------------------------------------------------------------------- 300 # ハンドラーテスト — パフォーマンス分析 301 # --------------------------------------------------------------------------- 302 303 304 @pytest.mark.unit 305 class TestGoogleAdsPerformanceAnalysisHandlers: 306 """パフォーマンス分析系ハンドラーテスト""" 307 308 async def test_performance_analyze(self) -> None: 309 mod = _import_google_ads_tools() 310 creds, client = _mock_google_ads_context() 311 client.analyze_performance.return_value = {"trend": "improving"} 312 313 h = _import_handlers() 314 with ( 315 patch.object(h, "load_google_ads_credentials", return_value=creds), 316 patch.object(h, "create_google_ads_client", return_value=client), 317 ): 318 result = await mod.handle_tool( 319 "google_ads.performance.analyze", 320 {"customer_id": "123", "campaign_id": "456"}, 321 ) 322 323 client.analyze_performance.assert_awaited_once() 324 parsed = json.loads(result[0].text) 325 assert parsed["trend"] == "improving" 326 327 async def test_cost_increase_investigate(self) -> None: 328 mod = _import_google_ads_tools() 329 creds, client = _mock_google_ads_context() 330 client.investigate_cost_increase.return_value = {"cause": "CPC rise"} 331 332 h = _import_handlers() 333 with ( 334 patch.object(h, "load_google_ads_credentials", return_value=creds), 335 patch.object(h, "create_google_ads_client", return_value=client), 336 ): 337 result = await mod.handle_tool( 338 "google_ads.cost_increase.investigate", 339 {"customer_id": "123", "campaign_id": "456"}, 340 ) 341 342 client.investigate_cost_increase.assert_awaited_once_with("456") 343 parsed = json.loads(result[0].text) 344 assert parsed["cause"] == "CPC rise" 345 346 async def test_health_check_all(self) -> None: 347 mod = _import_google_ads_tools() 348 creds, client = _mock_google_ads_context() 349 client.health_check_all_campaigns.return_value = {"status": "healthy"} 350 351 h = _import_handlers() 352 with ( 353 patch.object(h, "load_google_ads_credentials", return_value=creds), 354 patch.object(h, "create_google_ads_client", return_value=client), 355 ): 356 result = await mod.handle_tool( 357 "google_ads.health_check.all", 358 {"customer_id": "123"}, 359 ) 360 361 client.health_check_all_campaigns.assert_awaited_once() 362 parsed = json.loads(result[0].text) 363 assert parsed["status"] == "healthy" 364 365 async def test_ad_performance_compare(self) -> None: 366 mod = _import_google_ads_tools() 367 creds, client = _mock_google_ads_context() 368 client.compare_ad_performance.return_value = {"comparison": []} 369 370 h = _import_handlers() 371 with ( 372 patch.object(h, "load_google_ads_credentials", return_value=creds), 373 patch.object(h, "create_google_ads_client", return_value=client), 374 ): 375 result = await mod.handle_tool( 376 "google_ads.ad_performance.compare", 377 {"customer_id": "123", "ad_group_id": "10"}, 378 ) 379 380 client.compare_ad_performance.assert_awaited_once() 381 parsed = json.loads(result[0].text) 382 assert parsed["comparison"] == [] 383 384 385 # --------------------------------------------------------------------------- 386 # ハンドラーテスト — 予算分析 387 # --------------------------------------------------------------------------- 388 389 390 @pytest.mark.unit 391 class TestGoogleAdsBudgetAnalysisHandlers: 392 """予算分析系ハンドラーテスト""" 393 394 async def test_budget_efficiency(self) -> None: 395 mod = _import_google_ads_tools() 396 creds, client = _mock_google_ads_context() 397 client.analyze_budget_efficiency.return_value = {"efficiency": 0.85} 398 399 h = _import_handlers() 400 with ( 401 patch.object(h, "load_google_ads_credentials", return_value=creds), 402 patch.object(h, "create_google_ads_client", return_value=client), 403 ): 404 result = await mod.handle_tool( 405 "google_ads.budget.efficiency", 406 {"customer_id": "123"}, 407 ) 408 409 client.analyze_budget_efficiency.assert_awaited_once() 410 parsed = json.loads(result[0].text) 411 assert parsed["efficiency"] == 0.85 412 413 async def test_budget_reallocation(self) -> None: 414 mod = _import_google_ads_tools() 415 creds, client = _mock_google_ads_context() 416 client.suggest_budget_reallocation.return_value = {"suggestions": []} 417 418 h = _import_handlers() 419 with ( 420 patch.object(h, "load_google_ads_credentials", return_value=creds), 421 patch.object(h, "create_google_ads_client", return_value=client), 422 ): 423 result = await mod.handle_tool( 424 "google_ads.budget.reallocation", 425 {"customer_id": "123"}, 426 ) 427 428 client.suggest_budget_reallocation.assert_awaited_once() 429 parsed = json.loads(result[0].text) 430 assert parsed["suggestions"] == [] 431 432 433 # --------------------------------------------------------------------------- 434 # ハンドラーテスト — オークション分析 435 # --------------------------------------------------------------------------- 436 437 438 @pytest.mark.unit 439 class TestGoogleAdsAuctionInsightsGetHandler: 440 """オークション分析(get)ハンドラーテスト""" 441 442 async def test_auction_insights_get(self) -> None: 443 mod = _import_google_ads_tools() 444 creds, client = _mock_google_ads_context() 445 client.get_auction_insights.return_value = {"competitors": []} 446 447 h = _import_handlers() 448 with ( 449 patch.object(h, "load_google_ads_credentials", return_value=creds), 450 patch.object(h, "create_google_ads_client", return_value=client), 451 ): 452 result = await mod.handle_tool( 453 "google_ads.auction_insights.get", 454 {"customer_id": "123", "campaign_id": "456"}, 455 ) 456 457 client.get_auction_insights.assert_awaited_once() 458 parsed = json.loads(result[0].text) 459 assert parsed["competitors"] == [] 460 461 462 # --------------------------------------------------------------------------- 463 # ハンドラーテスト — RSA分析 464 # --------------------------------------------------------------------------- 465 466 467 @pytest.mark.unit 468 class TestGoogleAdsRsaAssetsHandlers: 469 """RSA分析系ハンドラーテスト""" 470 471 async def test_rsa_assets_analyze(self) -> None: 472 mod = _import_google_ads_tools() 473 creds, client = _mock_google_ads_context() 474 client.analyze_rsa_assets.return_value = {"assets": []} 475 476 h = _import_handlers() 477 with ( 478 patch.object(h, "load_google_ads_credentials", return_value=creds), 479 patch.object(h, "create_google_ads_client", return_value=client), 480 ): 481 result = await mod.handle_tool( 482 "google_ads.rsa_assets.analyze", 483 {"customer_id": "123", "campaign_id": "456"}, 484 ) 485 486 client.analyze_rsa_assets.assert_awaited_once() 487 parsed = json.loads(result[0].text) 488 assert parsed["assets"] == [] 489 490 async def test_rsa_assets_audit(self) -> None: 491 mod = _import_google_ads_tools() 492 creds, client = _mock_google_ads_context() 493 client.audit_rsa_assets.return_value = {"score": 90, "issues": []} 494 495 h = _import_handlers() 496 with ( 497 patch.object(h, "load_google_ads_credentials", return_value=creds), 498 patch.object(h, "create_google_ads_client", return_value=client), 499 ): 500 result = await mod.handle_tool( 501 "google_ads.rsa_assets.audit", 502 {"customer_id": "123", "campaign_id": "456"}, 503 ) 504 505 client.audit_rsa_assets.assert_awaited_once() 506 parsed = json.loads(result[0].text) 507 assert parsed["score"] == 90 508 509 510 # --------------------------------------------------------------------------- 511 # ハンドラーテスト — BtoB 512 # --------------------------------------------------------------------------- 513 514 515 @pytest.mark.unit 516 class TestGoogleAdsBtoBHandlers: 517 """BtoB最適化ハンドラーテスト""" 518 519 async def test_btob_optimizations(self) -> None: 520 mod = _import_google_ads_tools() 521 creds, client = _mock_google_ads_context() 522 client.suggest_btob_optimizations.return_value = {"suggestions": []} 523 524 h = _import_handlers() 525 with ( 526 patch.object(h, "load_google_ads_credentials", return_value=creds), 527 patch.object(h, "create_google_ads_client", return_value=client), 528 ): 529 result = await mod.handle_tool( 530 "google_ads.btob.optimizations", 531 {"customer_id": "123", "campaign_id": "456"}, 532 ) 533 534 client.suggest_btob_optimizations.assert_awaited_once() 535 parsed = json.loads(result[0].text) 536 assert parsed["suggestions"] == [] 537 538 539 # --------------------------------------------------------------------------- 540 # ハンドラーテスト — クリエイティブ 541 # --------------------------------------------------------------------------- 542 543 544 @pytest.mark.unit 545 class TestGoogleAdsCreativeHandlers: 546 """クリエイティブ系ハンドラーテスト""" 547 548 async def test_landing_page_analyze(self) -> None: 549 mod = _import_google_ads_tools() 550 creds, client = _mock_google_ads_context() 551 client.analyze_landing_page.return_value = {"score": 75, "issues": []} 552 553 h = _import_handlers() 554 with ( 555 patch.object(h, "load_google_ads_credentials", return_value=creds), 556 patch.object(h, "create_google_ads_client", return_value=client), 557 ): 558 result = await mod.handle_tool( 559 "google_ads.landing_page.analyze", 560 {"customer_id": "123", "url": "https://example.com"}, 561 ) 562 563 client.analyze_landing_page.assert_awaited_once_with("https://example.com") 564 parsed = json.loads(result[0].text) 565 assert parsed["score"] == 75 566 567 async def test_creative_research(self) -> None: 568 mod = _import_google_ads_tools() 569 creds, client = _mock_google_ads_context() 570 client.research_creative.return_value = {"suggestions": []} 571 572 h = _import_handlers() 573 with ( 574 patch.object(h, "load_google_ads_credentials", return_value=creds), 575 patch.object(h, "create_google_ads_client", return_value=client), 576 ): 577 result = await mod.handle_tool( 578 "google_ads.creative.research", 579 { 580 "customer_id": "123", 581 "campaign_id": "456", 582 "url": "https://example.com", 583 }, 584 ) 585 586 client.research_creative.assert_awaited_once() 587 parsed = json.loads(result[0].text) 588 assert parsed["suggestions"] == [] 589 590 591 # --------------------------------------------------------------------------- 592 # ハンドラーテスト — 監視 593 # --------------------------------------------------------------------------- 594 595 596 @pytest.mark.unit 597 class TestGoogleAdsMonitoringHandlers: 598 """監視系ハンドラーテスト""" 599 600 async def test_delivery_goal_evaluate(self) -> None: 601 mod = _import_google_ads_tools() 602 creds, client = _mock_google_ads_context() 603 client.evaluate_delivery_goal.return_value = {"on_track": True} 604 605 h = _import_handlers() 606 with ( 607 patch.object(h, "load_google_ads_credentials", return_value=creds), 608 patch.object(h, "create_google_ads_client", return_value=client), 609 ): 610 result = await mod.handle_tool( 611 "google_ads.monitoring.delivery_goal", 612 {"customer_id": "123", "campaign_id": "456"}, 613 ) 614 615 client.evaluate_delivery_goal.assert_awaited_once_with("456") 616 parsed = json.loads(result[0].text) 617 assert parsed["on_track"] is True 618 619 async def test_cpa_goal_evaluate(self) -> None: 620 mod = _import_google_ads_tools() 621 creds, client = _mock_google_ads_context() 622 client.evaluate_cpa_goal.return_value = {"within_target": True} 623 624 h = _import_handlers() 625 with ( 626 patch.object(h, "load_google_ads_credentials", return_value=creds), 627 patch.object(h, "create_google_ads_client", return_value=client), 628 ): 629 result = await mod.handle_tool( 630 "google_ads.monitoring.cpa_goal", 631 {"customer_id": "123", "campaign_id": "456", "target_cpa": 1000}, 632 ) 633 634 client.evaluate_cpa_goal.assert_awaited_once_with("456", 1000) 635 parsed = json.loads(result[0].text) 636 assert parsed["within_target"] is True 637 638 async def test_cv_goal_evaluate(self) -> None: 639 mod = _import_google_ads_tools() 640 creds, client = _mock_google_ads_context() 641 client.evaluate_cv_goal.return_value = {"on_track": False} 642 643 h = _import_handlers() 644 with ( 645 patch.object(h, "load_google_ads_credentials", return_value=creds), 646 patch.object(h, "create_google_ads_client", return_value=client), 647 ): 648 result = await mod.handle_tool( 649 "google_ads.monitoring.cv_goal", 650 {"customer_id": "123", "campaign_id": "456", "target_cv_daily": 10}, 651 ) 652 653 client.evaluate_cv_goal.assert_awaited_once_with("456", 10) 654 parsed = json.loads(result[0].text) 655 assert parsed["on_track"] is False 656 657 async def test_zero_conversions_diagnose(self) -> None: 658 mod = _import_google_ads_tools() 659 creds, client = _mock_google_ads_context() 660 client.diagnose_zero_conversions.return_value = {"diagnosis": "low traffic"} 661 662 h = _import_handlers() 663 with ( 664 patch.object(h, "load_google_ads_credentials", return_value=creds), 665 patch.object(h, "create_google_ads_client", return_value=client), 666 ): 667 result = await mod.handle_tool( 668 "google_ads.monitoring.zero_conversions", 669 {"customer_id": "123", "campaign_id": "456"}, 670 ) 671 672 client.diagnose_zero_conversions.assert_awaited_once_with("456") 673 parsed = json.loads(result[0].text) 674 assert parsed["diagnosis"] == "low traffic" 675 676 677 # --------------------------------------------------------------------------- 678 # ハンドラーテスト — キャプチャ 679 # --------------------------------------------------------------------------- 680 681 682 @pytest.mark.unit 683 class TestGoogleAdsCaptureHandlers: 684 """キャプチャ系ハンドラーテスト""" 685 686 async def test_capture_screenshot(self) -> None: 687 mod = _import_google_ads_tools() 688 creds, client = _mock_google_ads_context() 689 mock_screenshotter = AsyncMock() 690 mock_screenshotter.capture.return_value = b"\x89PNG\r\n\x1a\n" 691 692 h = _import_handlers() 693 with ( 694 patch.object(h, "load_google_ads_credentials", return_value=creds), 695 patch.object(h, "create_google_ads_client", return_value=client), 696 patch( 697 "mureo.google_ads._message_match.LPScreenshotter", 698 return_value=mock_screenshotter, 699 ), 700 ): 701 result = await mod.handle_tool( 702 "google_ads.capture.screenshot", 703 {"customer_id": "123", "url": "https://example.com"}, 704 ) 705 706 mock_screenshotter.capture.assert_awaited_once_with("https://example.com") 707 parsed = json.loads(result[0].text) 708 assert parsed["url"] == "https://example.com" 709 assert parsed["format"] == "png" 710 assert "screenshot_base64" in parsed