test_mcp_tools_google_ads_extensions.py
1 """Google Ads MCP拡張ツール ハンドラーテスト 2 3 サイトリンク、コールアウト、コンバージョン、ターゲティング、変更履歴ハンドラーの検証。 4 """ 5 6 from __future__ import annotations 7 8 import json 9 from unittest.mock import AsyncMock, MagicMock, patch 10 11 import pytest 12 13 14 def _import_google_ads_tools(): 15 from mureo.mcp import tools_google_ads 16 17 return tools_google_ads 18 19 20 def _import_handlers(): 21 from mureo.mcp import _handlers_google_ads 22 23 return _handlers_google_ads 24 25 26 def _mock_google_ads_context(): 27 """Google Ads認証情報とクライアントのモックを返す""" 28 mock_client = AsyncMock() 29 mock_creds = MagicMock() 30 return mock_creds, mock_client 31 32 33 # --------------------------------------------------------------------------- 34 # ハンドラーテスト — サイトリンク 35 # --------------------------------------------------------------------------- 36 37 38 @pytest.mark.unit 39 class TestGoogleAdsSitelinkHandlers: 40 """サイトリンク系ハンドラーテスト""" 41 42 async def test_sitelinks_list(self) -> None: 43 mod = _import_google_ads_tools() 44 creds, client = _mock_google_ads_context() 45 client.list_sitelinks.return_value = [{"id": "1", "link_text": "Top"}] 46 47 h = _import_handlers() 48 with ( 49 patch.object(h, "load_google_ads_credentials", return_value=creds), 50 patch.object(h, "create_google_ads_client", return_value=client), 51 ): 52 result = await mod.handle_tool( 53 "google_ads.sitelinks.list", 54 {"customer_id": "123", "campaign_id": "456"}, 55 ) 56 57 client.list_sitelinks.assert_awaited_once_with("456") 58 parsed = json.loads(result[0].text) 59 assert parsed[0]["link_text"] == "Top" 60 61 async def test_sitelinks_create(self) -> None: 62 mod = _import_google_ads_tools() 63 creds, client = _mock_google_ads_context() 64 client.create_sitelink.return_value = {"resource_name": "res"} 65 66 h = _import_handlers() 67 with ( 68 patch.object(h, "load_google_ads_credentials", return_value=creds), 69 patch.object(h, "create_google_ads_client", return_value=client), 70 ): 71 result = await mod.handle_tool( 72 "google_ads.sitelinks.create", 73 { 74 "customer_id": "123", 75 "campaign_id": "456", 76 "link_text": "About", 77 "final_url": "https://example.com/about", 78 }, 79 ) 80 81 client.create_sitelink.assert_awaited_once() 82 parsed = json.loads(result[0].text) 83 assert "resource_name" in parsed 84 85 async def test_sitelinks_remove(self) -> None: 86 mod = _import_google_ads_tools() 87 creds, client = _mock_google_ads_context() 88 client.remove_sitelink.return_value = {"resource_name": "res"} 89 90 h = _import_handlers() 91 with ( 92 patch.object(h, "load_google_ads_credentials", return_value=creds), 93 patch.object(h, "create_google_ads_client", return_value=client), 94 ): 95 result = await mod.handle_tool( 96 "google_ads.sitelinks.remove", 97 {"customer_id": "123", "campaign_id": "456", "asset_id": "789"}, 98 ) 99 100 client.remove_sitelink.assert_awaited_once() 101 parsed = json.loads(result[0].text) 102 assert "resource_name" in parsed 103 104 105 # --------------------------------------------------------------------------- 106 # ハンドラーテスト — コールアウト 107 # --------------------------------------------------------------------------- 108 109 110 @pytest.mark.unit 111 class TestGoogleAdsCalloutHandlers: 112 """コールアウト系ハンドラーテスト""" 113 114 async def test_callouts_list(self) -> None: 115 mod = _import_google_ads_tools() 116 creds, client = _mock_google_ads_context() 117 client.list_callouts.return_value = [{"id": "1", "text": "Free shipping"}] 118 119 h = _import_handlers() 120 with ( 121 patch.object(h, "load_google_ads_credentials", return_value=creds), 122 patch.object(h, "create_google_ads_client", return_value=client), 123 ): 124 result = await mod.handle_tool( 125 "google_ads.callouts.list", 126 {"customer_id": "123", "campaign_id": "456"}, 127 ) 128 129 client.list_callouts.assert_awaited_once_with("456") 130 parsed = json.loads(result[0].text) 131 assert len(parsed) == 1 132 133 async def test_callouts_create(self) -> None: 134 mod = _import_google_ads_tools() 135 creds, client = _mock_google_ads_context() 136 client.create_callout.return_value = {"resource_name": "res"} 137 138 h = _import_handlers() 139 with ( 140 patch.object(h, "load_google_ads_credentials", return_value=creds), 141 patch.object(h, "create_google_ads_client", return_value=client), 142 ): 143 result = await mod.handle_tool( 144 "google_ads.callouts.create", 145 { 146 "customer_id": "123", 147 "campaign_id": "456", 148 "callout_text": "Free shipping", 149 }, 150 ) 151 152 client.create_callout.assert_awaited_once() 153 parsed = json.loads(result[0].text) 154 assert "resource_name" in parsed 155 156 async def test_callouts_remove(self) -> None: 157 mod = _import_google_ads_tools() 158 creds, client = _mock_google_ads_context() 159 client.remove_callout.return_value = {"resource_name": "res"} 160 161 h = _import_handlers() 162 with ( 163 patch.object(h, "load_google_ads_credentials", return_value=creds), 164 patch.object(h, "create_google_ads_client", return_value=client), 165 ): 166 result = await mod.handle_tool( 167 "google_ads.callouts.remove", 168 {"customer_id": "123", "campaign_id": "456", "asset_id": "789"}, 169 ) 170 171 client.remove_callout.assert_awaited_once() 172 parsed = json.loads(result[0].text) 173 assert "resource_name" in parsed 174 175 176 # --------------------------------------------------------------------------- 177 # ハンドラーテスト — コンバージョン 178 # --------------------------------------------------------------------------- 179 180 181 @pytest.mark.unit 182 class TestGoogleAdsConversionHandlers: 183 """コンバージョン系ハンドラーテスト""" 184 185 async def test_conversions_list(self) -> None: 186 mod = _import_google_ads_tools() 187 creds, client = _mock_google_ads_context() 188 client.list_conversion_actions.return_value = [{"id": "1", "name": "Purchase"}] 189 190 h = _import_handlers() 191 with ( 192 patch.object(h, "load_google_ads_credentials", return_value=creds), 193 patch.object(h, "create_google_ads_client", return_value=client), 194 ): 195 result = await mod.handle_tool( 196 "google_ads.conversions.list", 197 {"customer_id": "123"}, 198 ) 199 200 client.list_conversion_actions.assert_awaited_once() 201 parsed = json.loads(result[0].text) 202 assert len(parsed) == 1 203 204 async def test_conversions_get(self) -> None: 205 mod = _import_google_ads_tools() 206 creds, client = _mock_google_ads_context() 207 client.get_conversion_action.return_value = {"id": "10", "name": "Purchase"} 208 209 h = _import_handlers() 210 with ( 211 patch.object(h, "load_google_ads_credentials", return_value=creds), 212 patch.object(h, "create_google_ads_client", return_value=client), 213 ): 214 result = await mod.handle_tool( 215 "google_ads.conversions.get", 216 {"customer_id": "123", "conversion_action_id": "10"}, 217 ) 218 219 client.get_conversion_action.assert_awaited_once_with("10") 220 parsed = json.loads(result[0].text) 221 assert parsed["id"] == "10" 222 223 async def test_conversions_performance(self) -> None: 224 mod = _import_google_ads_tools() 225 creds, client = _mock_google_ads_context() 226 client.get_conversion_performance.return_value = {"conversions": 50} 227 228 h = _import_handlers() 229 with ( 230 patch.object(h, "load_google_ads_credentials", return_value=creds), 231 patch.object(h, "create_google_ads_client", return_value=client), 232 ): 233 result = await mod.handle_tool( 234 "google_ads.conversions.performance", 235 {"customer_id": "123"}, 236 ) 237 238 client.get_conversion_performance.assert_awaited_once() 239 parsed = json.loads(result[0].text) 240 assert parsed["conversions"] == 50 241 242 async def test_conversions_create(self) -> None: 243 mod = _import_google_ads_tools() 244 creds, client = _mock_google_ads_context() 245 client.create_conversion_action.return_value = {"resource_name": "res"} 246 247 h = _import_handlers() 248 with ( 249 patch.object(h, "load_google_ads_credentials", return_value=creds), 250 patch.object(h, "create_google_ads_client", return_value=client), 251 ): 252 result = await mod.handle_tool( 253 "google_ads.conversions.create", 254 {"customer_id": "123", "name": "New Conversion"}, 255 ) 256 257 client.create_conversion_action.assert_awaited_once() 258 parsed = json.loads(result[0].text) 259 assert "resource_name" in parsed 260 261 async def test_conversions_update(self) -> None: 262 mod = _import_google_ads_tools() 263 creds, client = _mock_google_ads_context() 264 client.update_conversion_action.return_value = {"resource_name": "res"} 265 266 h = _import_handlers() 267 with ( 268 patch.object(h, "load_google_ads_credentials", return_value=creds), 269 patch.object(h, "create_google_ads_client", return_value=client), 270 ): 271 result = await mod.handle_tool( 272 "google_ads.conversions.update", 273 { 274 "customer_id": "123", 275 "conversion_action_id": "10", 276 "name": "Updated", 277 }, 278 ) 279 280 client.update_conversion_action.assert_awaited_once() 281 parsed = json.loads(result[0].text) 282 assert "resource_name" in parsed 283 284 async def test_conversions_remove(self) -> None: 285 mod = _import_google_ads_tools() 286 creds, client = _mock_google_ads_context() 287 client.remove_conversion_action.return_value = {"resource_name": "res"} 288 289 h = _import_handlers() 290 with ( 291 patch.object(h, "load_google_ads_credentials", return_value=creds), 292 patch.object(h, "create_google_ads_client", return_value=client), 293 ): 294 result = await mod.handle_tool( 295 "google_ads.conversions.remove", 296 {"customer_id": "123", "conversion_action_id": "10"}, 297 ) 298 299 client.remove_conversion_action.assert_awaited_once() 300 parsed = json.loads(result[0].text) 301 assert "resource_name" in parsed 302 303 async def test_conversions_tag(self) -> None: 304 mod = _import_google_ads_tools() 305 creds, client = _mock_google_ads_context() 306 client.get_conversion_action_tag.return_value = {"tag": "<script>...</script>"} 307 308 h = _import_handlers() 309 with ( 310 patch.object(h, "load_google_ads_credentials", return_value=creds), 311 patch.object(h, "create_google_ads_client", return_value=client), 312 ): 313 result = await mod.handle_tool( 314 "google_ads.conversions.tag", 315 {"customer_id": "123", "conversion_action_id": "10"}, 316 ) 317 318 client.get_conversion_action_tag.assert_awaited_once_with("10") 319 parsed = json.loads(result[0].text) 320 assert "tag" in parsed 321 322 323 # --------------------------------------------------------------------------- 324 # ハンドラーテスト — ターゲティング 325 # --------------------------------------------------------------------------- 326 327 328 @pytest.mark.unit 329 class TestGoogleAdsTargetingHandlers: 330 """ターゲティング系ハンドラーテスト""" 331 332 async def test_recommendations_list(self) -> None: 333 mod = _import_google_ads_tools() 334 creds, client = _mock_google_ads_context() 335 client.list_recommendations.return_value = [{"type": "KEYWORD"}] 336 337 h = _import_handlers() 338 with ( 339 patch.object(h, "load_google_ads_credentials", return_value=creds), 340 patch.object(h, "create_google_ads_client", return_value=client), 341 ): 342 result = await mod.handle_tool( 343 "google_ads.recommendations.list", 344 {"customer_id": "123"}, 345 ) 346 347 client.list_recommendations.assert_awaited_once() 348 parsed = json.loads(result[0].text) 349 assert parsed[0]["type"] == "KEYWORD" 350 351 async def test_recommendations_apply(self) -> None: 352 mod = _import_google_ads_tools() 353 creds, client = _mock_google_ads_context() 354 client.apply_recommendation.return_value = {"resource_name": "res"} 355 356 h = _import_handlers() 357 with ( 358 patch.object(h, "load_google_ads_credentials", return_value=creds), 359 patch.object(h, "create_google_ads_client", return_value=client), 360 ): 361 result = await mod.handle_tool( 362 "google_ads.recommendations.apply", 363 { 364 "customer_id": "123", 365 "resource_name": "customers/123/recommendations/456", 366 }, 367 ) 368 369 client.apply_recommendation.assert_awaited_once() 370 parsed = json.loads(result[0].text) 371 assert "resource_name" in parsed 372 373 async def test_device_targeting_get(self) -> None: 374 mod = _import_google_ads_tools() 375 creds, client = _mock_google_ads_context() 376 client.get_device_targeting.return_value = {"devices": ["MOBILE", "DESKTOP"]} 377 378 h = _import_handlers() 379 with ( 380 patch.object(h, "load_google_ads_credentials", return_value=creds), 381 patch.object(h, "create_google_ads_client", return_value=client), 382 ): 383 result = await mod.handle_tool( 384 "google_ads.device_targeting.get", 385 {"customer_id": "123", "campaign_id": "456"}, 386 ) 387 388 client.get_device_targeting.assert_awaited_once_with("456") 389 parsed = json.loads(result[0].text) 390 assert "MOBILE" in parsed["devices"] 391 392 async def test_device_targeting_set(self) -> None: 393 mod = _import_google_ads_tools() 394 creds, client = _mock_google_ads_context() 395 client.set_device_targeting.return_value = {"resource_name": "res"} 396 397 h = _import_handlers() 398 with ( 399 patch.object(h, "load_google_ads_credentials", return_value=creds), 400 patch.object(h, "create_google_ads_client", return_value=client), 401 ): 402 result = await mod.handle_tool( 403 "google_ads.device_targeting.set", 404 { 405 "customer_id": "123", 406 "campaign_id": "456", 407 "enabled_devices": ["MOBILE", "DESKTOP"], 408 }, 409 ) 410 411 client.set_device_targeting.assert_awaited_once() 412 parsed = json.loads(result[0].text) 413 assert "resource_name" in parsed 414 415 async def test_bid_adjustments_get(self) -> None: 416 mod = _import_google_ads_tools() 417 creds, client = _mock_google_ads_context() 418 client.get_bid_adjustments.return_value = [{"criterion_id": "1"}] 419 420 h = _import_handlers() 421 with ( 422 patch.object(h, "load_google_ads_credentials", return_value=creds), 423 patch.object(h, "create_google_ads_client", return_value=client), 424 ): 425 result = await mod.handle_tool( 426 "google_ads.bid_adjustments.get", 427 {"customer_id": "123", "campaign_id": "456"}, 428 ) 429 430 client.get_bid_adjustments.assert_awaited_once_with("456") 431 parsed = json.loads(result[0].text) 432 assert parsed[0]["criterion_id"] == "1" 433 434 async def test_bid_adjustments_update(self) -> None: 435 mod = _import_google_ads_tools() 436 creds, client = _mock_google_ads_context() 437 client.update_bid_adjustment.return_value = {"resource_name": "res"} 438 439 h = _import_handlers() 440 with ( 441 patch.object(h, "load_google_ads_credentials", return_value=creds), 442 patch.object(h, "create_google_ads_client", return_value=client), 443 ): 444 result = await mod.handle_tool( 445 "google_ads.bid_adjustments.update", 446 { 447 "customer_id": "123", 448 "campaign_id": "456", 449 "criterion_id": "1", 450 "bid_modifier": 1.2, 451 }, 452 ) 453 454 client.update_bid_adjustment.assert_awaited_once() 455 parsed = json.loads(result[0].text) 456 assert "resource_name" in parsed 457 458 async def test_location_targeting_list(self) -> None: 459 mod = _import_google_ads_tools() 460 creds, client = _mock_google_ads_context() 461 client.list_location_targeting.return_value = [{"location": "Tokyo"}] 462 463 h = _import_handlers() 464 with ( 465 patch.object(h, "load_google_ads_credentials", return_value=creds), 466 patch.object(h, "create_google_ads_client", return_value=client), 467 ): 468 result = await mod.handle_tool( 469 "google_ads.location_targeting.list", 470 {"customer_id": "123", "campaign_id": "456"}, 471 ) 472 473 client.list_location_targeting.assert_awaited_once_with("456") 474 parsed = json.loads(result[0].text) 475 assert parsed[0]["location"] == "Tokyo" 476 477 async def test_location_targeting_update(self) -> None: 478 mod = _import_google_ads_tools() 479 creds, client = _mock_google_ads_context() 480 client.update_location_targeting.return_value = {"resource_name": "res"} 481 482 h = _import_handlers() 483 with ( 484 patch.object(h, "load_google_ads_credentials", return_value=creds), 485 patch.object(h, "create_google_ads_client", return_value=client), 486 ): 487 result = await mod.handle_tool( 488 "google_ads.location_targeting.update", 489 { 490 "customer_id": "123", 491 "campaign_id": "456", 492 "add_locations": [{"geo_target_id": "1009312"}], 493 }, 494 ) 495 496 client.update_location_targeting.assert_awaited_once() 497 parsed = json.loads(result[0].text) 498 assert "resource_name" in parsed 499 500 async def test_schedule_targeting_list(self) -> None: 501 mod = _import_google_ads_tools() 502 creds, client = _mock_google_ads_context() 503 client.list_schedule_targeting.return_value = [{"day": "MONDAY"}] 504 505 h = _import_handlers() 506 with ( 507 patch.object(h, "load_google_ads_credentials", return_value=creds), 508 patch.object(h, "create_google_ads_client", return_value=client), 509 ): 510 result = await mod.handle_tool( 511 "google_ads.schedule_targeting.list", 512 {"customer_id": "123", "campaign_id": "456"}, 513 ) 514 515 client.list_schedule_targeting.assert_awaited_once_with("456") 516 parsed = json.loads(result[0].text) 517 assert parsed[0]["day"] == "MONDAY" 518 519 async def test_schedule_targeting_update(self) -> None: 520 mod = _import_google_ads_tools() 521 creds, client = _mock_google_ads_context() 522 client.update_schedule_targeting.return_value = {"resource_name": "res"} 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.schedule_targeting.update", 531 { 532 "customer_id": "123", 533 "campaign_id": "456", 534 "add_schedules": [ 535 {"day": "MONDAY", "start_hour": 9, "end_hour": 17} 536 ], 537 }, 538 ) 539 540 client.update_schedule_targeting.assert_awaited_once() 541 parsed = json.loads(result[0].text) 542 assert "resource_name" in parsed 543 544 async def test_change_history_list(self) -> None: 545 mod = _import_google_ads_tools() 546 creds, client = _mock_google_ads_context() 547 client.list_change_history.return_value = [{"change_type": "UPDATE"}] 548 549 h = _import_handlers() 550 with ( 551 patch.object(h, "load_google_ads_credentials", return_value=creds), 552 patch.object(h, "create_google_ads_client", return_value=client), 553 ): 554 result = await mod.handle_tool( 555 "google_ads.change_history.list", 556 {"customer_id": "123"}, 557 ) 558 559 client.list_change_history.assert_awaited_once() 560 parsed = json.loads(result[0].text) 561 assert parsed[0]["change_type"] == "UPDATE"