/ tests / test_mcp_tools_google_ads_analysis.py
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