Skip to main content
Glama
test_budget_update.py17.4 kB
#!/usr/bin/env python3 """ Unit Tests for Budget Update Functionality This test suite validates the budget update parameter implementation for the update_adset function in meta_ads_mcp/core/adsets.py. Test cases cover: - Budget update success scenarios - Budget validation (negative, zero, too high values) - Budget update with other parameters - Error handling and permissions """ import pytest import json import asyncio from unittest.mock import AsyncMock, patch, MagicMock from typing import Dict, Any, List # Import the function to test from meta_ads_mcp.core.adsets import update_adset class TestBudgetUpdateFunctionality: """Test suite for budget update functionality""" @pytest.fixture def mock_api_request(self): """Mock for the make_api_request function""" with patch('meta_ads_mcp.core.adsets.make_api_request') as mock: mock.return_value = { "id": "test_adset_id", "daily_budget": "5000", "status": "ACTIVE" } yield mock @pytest.fixture def mock_auth_manager(self): """Mock for the authentication manager""" with patch('meta_ads_mcp.core.api.auth_manager') as mock, \ patch('meta_ads_mcp.core.auth.get_current_access_token') as mock_get_token: # Mock a valid access token mock.get_current_access_token.return_value = "test_access_token" mock.is_token_valid.return_value = True mock.app_id = "test_app_id" mock_get_token.return_value = "test_access_token" yield mock @pytest.fixture def valid_adset_id(self): """Valid ad set ID for testing""" return "123456789" @pytest.fixture def valid_daily_budget(self): """Valid daily budget amount in cents""" return "5000" # $50.00 @pytest.fixture def valid_lifetime_budget(self): """Valid lifetime budget amount in cents""" return "50000" # $500.00 @pytest.mark.asyncio async def test_budget_update_success(self, mock_api_request, mock_auth_manager, valid_adset_id, valid_daily_budget): """Test successful budget update""" result = await update_adset( adset_id=valid_adset_id, daily_budget=valid_daily_budget ) # Parse the result result_data = json.loads(result) # Verify the API was called with correct parameters mock_api_request.assert_called_once() call_args = mock_api_request.call_args # Check that the endpoint is correct (first argument) assert call_args[0][0] == valid_adset_id # Check that daily_budget was included in parameters (third argument) params = call_args[0][2] # Third positional argument is params assert 'daily_budget' in params assert params['daily_budget'] == valid_daily_budget # Verify the response structure assert 'id' in result_data assert result_data['id'] == "test_adset_id" @pytest.mark.asyncio async def test_lifetime_budget_update_success(self, mock_api_request, mock_auth_manager, valid_adset_id, valid_lifetime_budget): """Test successful lifetime budget update""" result = await update_adset( adset_id=valid_adset_id, lifetime_budget=valid_lifetime_budget ) # Parse the result result_data = json.loads(result) # Verify the API was called with correct parameters mock_api_request.assert_called_once() call_args = mock_api_request.call_args # Check that lifetime_budget was included in parameters params = call_args[0][2] # Third positional argument is params assert 'lifetime_budget' in params assert params['lifetime_budget'] == valid_lifetime_budget # Verify the response structure assert 'id' in result_data @pytest.mark.asyncio async def test_both_budget_types_update(self, mock_api_request, mock_auth_manager, valid_adset_id, valid_daily_budget, valid_lifetime_budget): """Test updating both daily and lifetime budget simultaneously""" result = await update_adset( adset_id=valid_adset_id, daily_budget=valid_daily_budget, lifetime_budget=valid_lifetime_budget ) # Parse the result result_data = json.loads(result) # Verify the API was called with correct parameters mock_api_request.assert_called_once() call_args = mock_api_request.call_args # Check that both budget parameters were included params = call_args[0][2] # Third positional argument is params assert 'daily_budget' in params assert 'lifetime_budget' in params assert params['daily_budget'] == valid_daily_budget assert params['lifetime_budget'] == valid_lifetime_budget # Verify the response structure assert 'id' in result_data @pytest.mark.asyncio async def test_budget_update_with_other_parameters(self, mock_api_request, mock_auth_manager, valid_adset_id, valid_daily_budget): """Test budget update combined with other parameters""" result = await update_adset( adset_id=valid_adset_id, daily_budget=valid_daily_budget, status="PAUSED", bid_amount=1000, bid_strategy="LOWEST_COST_WITH_BID_CAP" ) # Parse the result result_data = json.loads(result) # Verify the API was called with correct parameters mock_api_request.assert_called_once() call_args = mock_api_request.call_args # Check that all parameters were included params = call_args[0][2] # Third positional argument is params assert 'daily_budget' in params assert 'status' in params assert 'bid_amount' in params assert 'bid_strategy' in params assert params['daily_budget'] == valid_daily_budget assert params['status'] == "PAUSED" assert params['bid_amount'] == "1000" assert params['bid_strategy'] == "LOWEST_COST_WITH_BID_CAP" # Verify the response structure assert 'id' in result_data @pytest.mark.asyncio async def test_budget_update_with_numeric_values(self, mock_api_request, mock_auth_manager, valid_adset_id): """Test budget update with numeric values (should be converted to strings)""" result = await update_adset( adset_id=valid_adset_id, daily_budget=5000, # Integer lifetime_budget=50000 # Integer ) # Parse the result result_data = json.loads(result) # Verify the API was called with correct parameters mock_api_request.assert_called_once() call_args = mock_api_request.call_args # Check that numeric values were converted to strings params = call_args[0][2] # Third positional argument is params assert 'daily_budget' in params assert 'lifetime_budget' in params assert params['daily_budget'] == "5000" assert params['lifetime_budget'] == "50000" # Verify the response structure assert 'id' in result_data @pytest.mark.asyncio async def test_budget_update_with_zero_budget(self, mock_api_request, mock_auth_manager, valid_adset_id): """Test budget update with zero budget (should be allowed)""" result = await update_adset( adset_id=valid_adset_id, daily_budget="0" ) # Parse the result result_data = json.loads(result) # Verify the API was called with zero budget mock_api_request.assert_called_once() call_args = mock_api_request.call_args params = call_args[0][2] # Third positional argument is params assert 'daily_budget' in params assert params['daily_budget'] == "0" # Verify the response structure assert 'id' in result_data @pytest.mark.asyncio async def test_budget_update_with_high_budget(self, mock_api_request, mock_auth_manager, valid_adset_id): """Test budget update with high budget values""" high_budget = "1000000" # $10,000 result = await update_adset( adset_id=valid_adset_id, daily_budget=high_budget ) # Parse the result result_data = json.loads(result) # Verify the API was called with high budget mock_api_request.assert_called_once() call_args = mock_api_request.call_args params = call_args[0][2] # Third positional argument is params assert 'daily_budget' in params assert params['daily_budget'] == high_budget # Verify the response structure assert 'id' in result_data @pytest.mark.asyncio async def test_budget_update_api_error_handling(self, mock_api_request, mock_auth_manager, valid_adset_id, valid_daily_budget): """Test error handling when API call fails""" # Mock API to raise an exception mock_api_request.side_effect = Exception("API Error: Invalid budget amount") result = await update_adset( adset_id=valid_adset_id, daily_budget=valid_daily_budget ) # Parse the result result_data = json.loads(result) # Verify error response structure # The error is wrapped in a 'data' field as JSON string error_data = json.loads(result_data['data']) assert 'error' in error_data assert 'details' in error_data assert 'params_sent' in error_data assert "Failed to update ad set" in error_data['error'] assert "API Error: Invalid budget amount" in error_data['details'] assert valid_adset_id in error_data['error'] # Verify the parameters that were sent assert error_data['params_sent']['daily_budget'] == valid_daily_budget @pytest.mark.asyncio async def test_budget_update_with_invalid_adset_id(self, mock_api_request, mock_auth_manager): """Test budget update with invalid ad set ID""" result = await update_adset( adset_id="", # Empty ad set ID daily_budget="5000" ) # Parse the result result_data = json.loads(result) # Verify error response error_data = json.loads(result_data['data']) assert 'error' in error_data assert "No ad set ID provided" in error_data['error'] # Verify API was not called mock_api_request.assert_not_called() @pytest.mark.asyncio async def test_budget_update_with_no_parameters(self, mock_api_request, mock_auth_manager, valid_adset_id): """Test budget update with no parameters provided""" result = await update_adset( adset_id=valid_adset_id # No parameters provided ) # Parse the result result_data = json.loads(result) # Verify error response error_data = json.loads(result_data['data']) assert 'error' in error_data assert "No update parameters provided" in error_data['error'] # Verify API was not called mock_api_request.assert_not_called() @pytest.mark.asyncio async def test_budget_update_with_negative_budget(self, mock_api_request, mock_auth_manager, valid_adset_id): """Test budget update with negative budget (should be handled by API)""" # Mock API to handle negative budget error mock_api_request.side_effect = Exception("API Error: Budget amount must be positive") result = await update_adset( adset_id=valid_adset_id, daily_budget="-1000" ) # Parse the result result_data = json.loads(result) # Verify error response structure error_data = json.loads(result_data['data']) assert 'error' in error_data assert 'details' in error_data assert "API Error: Budget amount must be positive" in error_data['details'] @pytest.mark.asyncio async def test_budget_update_with_non_numeric_string(self, mock_api_request, mock_auth_manager, valid_adset_id): """Test budget update with non-numeric string (should be handled by API)""" # Mock API to handle non-numeric budget error mock_api_request.side_effect = Exception("API Error: Invalid budget format") result = await update_adset( adset_id=valid_adset_id, daily_budget="invalid_budget" ) # Parse the result result_data = json.loads(result) # Verify error response structure error_data = json.loads(result_data['data']) assert 'error' in error_data assert 'details' in error_data assert "API Error: Invalid budget format" in error_data['details'] @pytest.mark.asyncio async def test_budget_update_with_permission_error(self, mock_api_request, mock_auth_manager, valid_adset_id, valid_daily_budget): """Test budget update with permission error""" # Mock API to raise permission error mock_api_request.side_effect = Exception("API Error: (#100) Insufficient permissions") result = await update_adset( adset_id=valid_adset_id, daily_budget=valid_daily_budget ) # Parse the result result_data = json.loads(result) # Verify error response structure error_data = json.loads(result_data['data']) assert 'error' in error_data assert 'details' in error_data assert "Insufficient permissions" in error_data['details'] @pytest.mark.asyncio async def test_budget_update_with_targeting(self, mock_api_request, mock_auth_manager, valid_adset_id, valid_daily_budget): """Test budget update combined with targeting update""" targeting = { "age_min": 25, "age_max": 45, "geo_locations": {"countries": ["US", "CA"]} } result = await update_adset( adset_id=valid_adset_id, daily_budget=valid_daily_budget, targeting=targeting ) # Parse the result result_data = json.loads(result) # Verify the API was called with correct parameters mock_api_request.assert_called_once() call_args = mock_api_request.call_args # Check that both budget and targeting were included params = call_args[0][2] # Third positional argument is params assert 'daily_budget' in params assert 'targeting' in params assert params['daily_budget'] == valid_daily_budget # Verify targeting was properly JSON encoded targeting_json = json.loads(params['targeting']) assert targeting_json['age_min'] == 25 assert targeting_json['age_max'] == 45 assert targeting_json['geo_locations']['countries'] == ["US", "CA"] # Verify the response structure assert 'id' in result_data class TestBudgetUpdateIntegration: """Integration tests for budget update functionality""" @pytest.mark.asyncio async def test_budget_update_workflow(self): """Test complete budget update workflow""" # This test would require a real ad set ID and valid API credentials # For now, we'll test the function signature and parameter handling # Test that the function accepts the new parameters with patch('meta_ads_mcp.core.adsets.make_api_request') as mock_api, \ patch('meta_ads_mcp.core.api.auth_manager') as mock_auth, \ patch('meta_ads_mcp.core.auth.get_current_access_token') as mock_get_token: mock_api.return_value = {"id": "test_id", "daily_budget": "5000"} mock_auth.get_current_access_token.return_value = "test_access_token" mock_auth.is_token_valid.return_value = True mock_auth.app_id = "test_app_id" mock_get_token.return_value = "test_access_token" result = await update_adset( adset_id="test_adset_id", daily_budget="5000", lifetime_budget="50000" ) # Verify the function executed without errors assert result is not None # The result might already be a dict or a JSON string if isinstance(result, str): result_data = json.loads(result) else: result_data = result assert 'id' in result_data if __name__ == "__main__": pytest.main([__file__, "-v"])

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pipeboard-co/meta-ads-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server