Skip to main content
Glama

Google Calendar MCP Server

test_calendar_tools.py23.7 kB
import pytest import os from unittest.mock import Mock, patch, MagicMock from datetime import datetime, timedelta, timezone from googleapiclient.errors import HttpError from src.tools.calendar_tools import ( list_calendars, get_events, create_event, delete_event ) # 実際のAPIを使用するかどうかの判定 USE_REAL_API = os.getenv('USE_REAL_API', 'false').lower() in ('true', '1', 'yes') class TestListCalendars: @patch('src.tools.calendar_tools.get_service') def test_list_calendars_success(self, mock_get_service): # モックのセットアップ mock_service = Mock() mock_calendar_list = Mock() mock_calendar_list.list.return_value.execute.return_value = { 'items': [ { 'id': 'calendar1@example.com', 'summary': 'Test Calendar 1', 'description': 'Test description 1', 'timeZone': 'Asia/Tokyo', 'accessRole': 'owner' }, { 'id': 'calendar2@example.com', 'summary': 'Test Calendar 2', 'description': '', 'timeZone': 'UTC', 'accessRole': 'reader' } ] } mock_service.calendarList.return_value = mock_calendar_list mock_get_service.return_value = mock_service # テスト実行 result = list_calendars() # 検証 assert result['success'] is True assert result['count'] == 2 assert len(result['calendars']) == 2 calendar1 = result['calendars'][0] assert calendar1['id'] == 'calendar1@example.com' assert calendar1['summary'] == 'Test Calendar 1' assert calendar1['description'] == 'Test description 1' assert calendar1['timeZone'] == 'Asia/Tokyo' assert calendar1['accessRole'] == 'owner' @patch('src.tools.calendar_tools.get_service') def test_list_calendars_empty_result(self, mock_get_service): # モックのセットアップ mock_service = Mock() mock_calendar_list = Mock() mock_calendar_list.list.return_value.execute.return_value = {'items': []} mock_service.calendarList.return_value = mock_calendar_list mock_get_service.return_value = mock_service # テスト実行 result = list_calendars() # 検証 assert result['success'] is True assert result['count'] == 0 assert result['calendars'] == [] @patch('src.tools.calendar_tools.get_service') def test_list_calendars_http_error(self, mock_get_service): # モックのセットアップ mock_get_service.side_effect = HttpError( resp=Mock(status=403), content=b'Forbidden' ) # テスト実行 result = list_calendars() # 検証 assert result['success'] is False assert 'error' in result assert result['calendars'] == [] class TestGetEvents: @patch('src.tools.calendar_tools.get_service') def test_get_events_success(self, mock_get_service): # モックのセットアップ mock_service = Mock() mock_events = Mock() mock_events.list.return_value.execute.return_value = { 'items': [ { 'id': 'event1', 'summary': 'Test Event 1', 'description': 'Test description 1', 'start': {'dateTime': '2024-01-01T09:00:00Z'}, 'end': {'dateTime': '2024-01-01T10:00:00Z'}, 'location': 'Test Location', 'attendees': [ {'email': 'user1@example.com'}, {'email': 'user2@example.com'} ] } ] } mock_service.events.return_value = mock_events mock_get_service.return_value = mock_service # テスト実行 result = get_events() # 検証 assert result['success'] is True assert result['count'] == 1 assert len(result['events']) == 1 event = result['events'][0] assert event['id'] == 'event1' assert event['summary'] == 'Test Event 1' assert event['description'] == 'Test description 1' assert event['start'] == '2024-01-01T09:00:00Z' assert event['end'] == '2024-01-01T10:00:00Z' assert event['location'] == 'Test Location' assert event['attendees'] == ['user1@example.com', 'user2@example.com'] @patch('src.tools.calendar_tools.get_service') def test_get_events_with_custom_params(self, mock_get_service): # モックのセットアップ mock_service = Mock() mock_events = Mock() mock_events.list.return_value.execute.return_value = {'items': []} mock_service.events.return_value = mock_events mock_get_service.return_value = mock_service # テスト実行 result = get_events( calendar_id='custom@example.com', time_min='2024-01-01T00:00:00Z', time_max='2024-01-02T00:00:00Z', max_results=5 ) # 検証 assert result['success'] is True mock_events.list.assert_called_once_with( calendarId='custom@example.com', timeMin='2024-01-01T00:00:00Z', timeMax='2024-01-02T00:00:00Z', maxResults=5, singleEvents=True, orderBy='startTime' ) @patch('src.tools.calendar_tools.get_service') def test_get_events_date_only_event(self, mock_get_service): # モックのセットアップ mock_service = Mock() mock_events = Mock() mock_events.list.return_value.execute.return_value = { 'items': [ { 'id': 'event1', 'summary': 'All Day Event', 'start': {'date': '2024-01-01'}, 'end': {'date': '2024-01-02'}, } ] } mock_service.events.return_value = mock_events mock_get_service.return_value = mock_service # テスト実行 result = get_events() # 検証 assert result['success'] is True event = result['events'][0] assert event['start'] == '2024-01-01' assert event['end'] == '2024-01-02' @patch('src.tools.calendar_tools.get_service') def test_get_events_http_error(self, mock_get_service): # モックのセットアップ mock_get_service.side_effect = HttpError( resp=Mock(status=404), content=b'Not Found' ) # テスト実行 result = get_events() # 検証 assert result['success'] is False assert 'error' in result assert result['events'] == [] class TestCreateEvent: @patch('src.tools.calendar_tools.get_service') def test_create_event_success(self, mock_get_service): # モックのセットアップ mock_service = Mock() mock_events = Mock() mock_events.insert.return_value.execute.return_value = { 'id': 'created_event_id', 'htmlLink': 'https://calendar.google.com/event?eid=created_event_id' } mock_service.events.return_value = mock_events mock_get_service.return_value = mock_service # テスト実行 result = create_event( summary='Test Event', start_time='2024-01-01T09:00:00Z', end_time='2024-01-01T10:00:00Z', description='Test description', location='Test location', calendar_id='primary', attendees=['user1@example.com', 'user2@example.com'] ) # 検証 assert result['success'] is True assert result['event_id'] == 'created_event_id' assert result['event_link'] == 'https://calendar.google.com/event?eid=created_event_id' assert 'Test Event' in result['message'] # イベント作成時の引数を検証 mock_events.insert.assert_called_once() call_args = mock_events.insert.call_args assert call_args[1]['calendarId'] == 'primary' event_body = call_args[1]['body'] assert event_body['summary'] == 'Test Event' assert event_body['description'] == 'Test description' assert event_body['location'] == 'Test location' assert event_body['start']['dateTime'] == '2024-01-01T09:00:00Z' assert event_body['end']['dateTime'] == '2024-01-01T10:00:00Z' assert len(event_body['attendees']) == 2 @patch('src.tools.calendar_tools.get_service') def test_create_event_minimal_params(self, mock_get_service): # モックのセットアップ mock_service = Mock() mock_events = Mock() mock_events.insert.return_value.execute.return_value = { 'id': 'minimal_event_id' } mock_service.events.return_value = mock_events mock_get_service.return_value = mock_service # テスト実行 result = create_event( summary='Minimal Event', start_time='2024-01-01T09:00:00Z', end_time='2024-01-01T10:00:00Z' ) # 検証 assert result['success'] is True assert result['event_id'] == 'minimal_event_id' # イベント作成時の引数を検証 call_args = mock_events.insert.call_args event_body = call_args[1]['body'] assert event_body['summary'] == 'Minimal Event' assert event_body['description'] == '' assert event_body['location'] == '' assert 'attendees' not in event_body @patch('src.tools.calendar_tools.get_service') def test_create_event_http_error(self, mock_get_service): # モックのセットアップ mock_get_service.side_effect = HttpError( resp=Mock(status=400), content=b'Bad Request' ) # テスト実行 result = create_event( summary='Test Event', start_time='2024-01-01T09:00:00Z', end_time='2024-01-01T10:00:00Z' ) # 検証 assert result['success'] is False assert 'error' in result assert result['event_id'] is None class TestDeleteEvent: @patch('src.tools.calendar_tools.get_service') def test_delete_event_success(self, mock_get_service): # モックのセットアップ mock_service = Mock() mock_events = Mock() mock_events.delete.return_value.execute.return_value = None mock_service.events.return_value = mock_events mock_get_service.return_value = mock_service # テスト実行 result = delete_event('test_event_id') # 検証 assert result['success'] is True assert 'test_event_id' in result['message'] # 削除時の引数を検証 mock_events.delete.assert_called_once_with( calendarId='primary', eventId='test_event_id' ) @patch('src.tools.calendar_tools.get_service') def test_delete_event_custom_calendar(self, mock_get_service): # モックのセットアップ mock_service = Mock() mock_events = Mock() mock_events.delete.return_value.execute.return_value = None mock_service.events.return_value = mock_events mock_get_service.return_value = mock_service # テスト実行 result = delete_event('test_event_id', 'custom@example.com') # 検証 assert result['success'] is True # 削除時の引数を検証 mock_events.delete.assert_called_once_with( calendarId='custom@example.com', eventId='test_event_id' ) @patch('src.tools.calendar_tools.get_service') def test_delete_event_http_error(self, mock_get_service): # モックのセットアップ mock_get_service.side_effect = HttpError( resp=Mock(status=404), content=b'Not Found' ) # テスト実行 result = delete_event('nonexistent_event_id') # 検証 assert result['success'] is False assert 'error' in result class TestIntegration: @patch('src.tools.calendar_tools.get_service') def test_workflow_create_and_delete_event(self, mock_get_service): # モックのセットアップ mock_service = Mock() mock_events = Mock() # 作成時のレスポンス mock_events.insert.return_value.execute.return_value = { 'id': 'workflow_event_id', 'htmlLink': 'https://calendar.google.com/event?eid=workflow_event_id' } # 削除時のレスポンス mock_events.delete.return_value.execute.return_value = None mock_service.events.return_value = mock_events mock_get_service.return_value = mock_service # イベント作成 create_result = create_event( summary='Workflow Test Event', start_time='2024-01-01T09:00:00Z', end_time='2024-01-01T10:00:00Z' ) # 作成結果の検証 assert create_result['success'] is True event_id = create_result['event_id'] # イベント削除 delete_result = delete_event(event_id) # 削除結果の検証 assert delete_result['success'] is True # 両方の操作が呼ばれたことを確認 mock_events.insert.assert_called_once() mock_events.delete.assert_called_once() # 実際のAPIを使用するテストクラス(環境変数で制御) @pytest.mark.real_api class TestRealAPI: @pytest.fixture(autouse=True) def check_real_api_mode(self): if not (os.getenv('USE_REAL_API', 'false').lower() in ('true', '1', 'yes')): pytest.skip("USE_REAL_API環境変数が設定されていません") @pytest.mark.integration def test_list_calendars_real_api(self): result = list_calendars() # 基本的な構造の検証 assert 'success' in result assert 'calendars' in result assert 'count' in result if result['success']: # 成功時の検証 assert isinstance(result['calendars'], list) assert result['count'] == len(result['calendars']) # カレンダー情報の構造を検証 for calendar in result['calendars']: assert 'id' in calendar assert 'summary' in calendar assert isinstance(calendar['id'], str) assert isinstance(calendar['summary'], str) else: # 失敗時の検証 assert 'error' in result print(f"API Error: {result['error']}") @pytest.mark.integration def test_get_events_real_api(self): result = get_events(max_results=5) # 基本的な構造の検証 assert 'success' in result assert 'events' in result assert 'count' in result if result['success']: # 成功時の検証 assert isinstance(result['events'], list) assert result['count'] == len(result['events']) assert result['count'] <= 5 # max_resultsの制限 # イベント情報の構造を検証 for event in result['events']: assert 'id' in event assert 'summary' in event assert 'start' in event assert 'end' in event assert isinstance(event['id'], str) assert isinstance(event['summary'], str) else: # 失敗時の検証 assert 'error' in result print(f"API Error: {result['error']}") @pytest.mark.integration def test_create_and_delete_event_real_api(self): # テスト用のイベント情報 test_summary = f"Test Event {datetime.now().strftime('%Y%m%d_%H%M%S')}" start_time = (datetime.now(timezone.utc) + timedelta(hours=1)).isoformat().replace('+00:00', 'Z') end_time = (datetime.now(timezone.utc) + timedelta(hours=2)).isoformat().replace('+00:00', 'Z') # イベント作成 create_result = create_event( summary=test_summary, start_time=start_time, end_time=end_time, description="Test event for API testing", location="Test Location" ) # 作成結果の検証 assert 'success' in create_result if create_result['success']: assert 'event_id' in create_result event_id = create_result['event_id'] assert event_id is not None # 作成されたイベントを確認 events_result = get_events() if events_result['success']: created_event = next( (e for e in events_result['events'] if e['id'] == event_id), None ) assert created_event is not None, "作成したイベントが見つかりません" assert created_event['summary'] == test_summary # イベント削除 delete_result = delete_event(event_id) assert 'success' in delete_result if delete_result['success']: print(f"イベント '{test_summary}' の作成・削除が正常に完了しました") else: print(f"イベント削除エラー: {delete_result.get('error', 'Unknown error')}") else: print(f"イベント作成エラー: {create_result.get('error', 'Unknown error')}") @pytest.mark.integration def test_get_events_with_time_range_real_api(self): # 過去1週間から未来1週間のイベントを取得 time_min = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat().replace('+00:00', 'Z') time_max = (datetime.now(timezone.utc) + timedelta(days=7)).isoformat().replace('+00:00', 'Z') result = get_events( time_min=time_min, time_max=time_max, max_results=10 ) # 基本的な構造の検証 assert 'success' in result assert 'events' in result if result['success']: print(f"時間範囲指定で {result['count']} 件のイベントを取得しました") # イベントの時間範囲を検証(タイムゾーンを考慮) min_datetime = datetime.fromisoformat(time_min.replace('Z', '+00:00')) max_datetime = datetime.fromisoformat(time_max.replace('Z', '+00:00')) validated_events = 0 for event in result['events']: if 'start' in event and event['start']: event_start = event['start'] if 'T' in event_start: # 時刻指定のイベント try: # タイムゾーン情報を適切に処理 if event_start.endswith('Z'): event_datetime = datetime.fromisoformat(event_start.replace('Z', '+00:00')) elif '+' in event_start or event_start.count('-') > 2: # 既にタイムゾーン情報がある場合 event_datetime = datetime.fromisoformat(event_start) else: # タイムゾーン情報がない場合はUTCとして扱う event_datetime = datetime.fromisoformat(event_start + '+00:00') # 時間範囲の検証(タイムゾーンを考慮して比較) # UTCに変換して比較 event_utc = event_datetime.astimezone(timezone.utc) min_utc = min_datetime.astimezone(timezone.utc) max_utc = max_datetime.astimezone(timezone.utc) # より柔軟な時間範囲チェック(1分のマージンを設ける) margin = timedelta(minutes=1) if event_utc >= (min_utc - margin) and event_utc <= (max_utc + margin): validated_events += 1 else: print(f"警告: イベント '{event.get('summary', 'No Title')}' の開始時刻 {event_start} が指定された時間範囲外です") except (ValueError, TypeError) as e: # 日付解析エラーの場合は警告を出力してスキップ print(f"警告: イベントの開始時刻 '{event_start}' の解析に失敗しました: {e}") continue # 検証可能なイベントがあることを確認 if validated_events > 0: print(f"{validated_events} 件のイベントの時間範囲を正常に検証しました") else: print("警告: 検証可能なイベントが見つかりませんでした") else: print(f"時間範囲指定でのイベント取得エラー: {result.get('error', 'Unknown error')}") @pytest.mark.integration def test_create_event_with_attendees_real_api(self): # テスト用のイベント情報 test_summary = f"Meeting Test {datetime.now().strftime('%Y%m%d_%H%M%S')}" start_time = (datetime.now(timezone.utc) + timedelta(hours=1)).isoformat().replace('+00:00', 'Z') end_time = (datetime.now(timezone.utc) + timedelta(hours=1, minutes=30)).isoformat().replace('+00:00', 'Z') # 参加者付きイベント作成 create_result = create_event( summary=test_summary, start_time=start_time, end_time=end_time, description="Test meeting with attendees", attendees=['test1@example.com', 'test2@example.com'] ) # 作成結果の検証 assert 'success' in create_result if create_result['success']: event_id = create_result['event_id'] print(f"参加者付きイベント '{test_summary}' を作成しました (ID: {event_id})") # 作成されたイベントを確認 events_result = get_events() if events_result['success']: created_event = next( (e for e in events_result['events'] if e['id'] == event_id), None ) if created_event: print(f"参加者: {created_event.get('attendees', [])}") # テスト用イベントを削除 delete_result = delete_event(event_id) if delete_result['success']: print(f"テスト用イベントを削除しました") else: print(f"参加者付きイベント作成エラー: {create_result.get('error', 'Unknown error')}")

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/YusukeYoshiyama/google-calendar-mcp'

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