Mateo/google calendar list (#367)
Adds "ListCalendars" to the calendar toolkit
This commit is contained in:
parent
2dc70a7814
commit
eb6b7f3987
7 changed files with 211 additions and 81 deletions
|
|
@ -6,17 +6,64 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||||
from arcade.sdk import ToolContext, tool
|
from arcade.sdk import ToolContext, tool
|
||||||
from arcade.sdk.auth import Google
|
from arcade.sdk.auth import Google
|
||||||
from arcade.sdk.errors import RetryableToolError
|
from arcade.sdk.errors import RetryableToolError
|
||||||
from google.oauth2.credentials import Credentials
|
|
||||||
from googleapiclient.discovery import build
|
|
||||||
from googleapiclient.errors import HttpError
|
from googleapiclient.errors import HttpError
|
||||||
|
|
||||||
from arcade_google.models import EventVisibility, SendUpdatesOptions
|
from arcade_google.models import EventVisibility, SendUpdatesOptions
|
||||||
from arcade_google.utils import (
|
from arcade_google.utils import (
|
||||||
|
build_calendar_service,
|
||||||
|
build_oauth_service,
|
||||||
compute_free_time_intersection,
|
compute_free_time_intersection,
|
||||||
parse_datetime,
|
parse_datetime,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@tool(
|
||||||
|
requires_auth=Google(
|
||||||
|
scopes=[
|
||||||
|
"https://www.googleapis.com/auth/calendar.readonly",
|
||||||
|
"https://www.googleapis.com/auth/calendar.events",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
async def list_calendars(
|
||||||
|
context: ToolContext,
|
||||||
|
max_results: Annotated[
|
||||||
|
int, "The maximum number of calendars to return. " "Up to 250 calendars, defaults to 10."
|
||||||
|
] = 10,
|
||||||
|
show_deleted: Annotated[
|
||||||
|
bool, "Whether to show deleted calendars." " Defaults to False"
|
||||||
|
] = False,
|
||||||
|
show_hidden: Annotated[bool, "Whether to show hidden calendars." " Defaults to False"] = False,
|
||||||
|
next_page_token: Annotated[
|
||||||
|
str | None, "The token to retrieve the next page of calendars. Optional."
|
||||||
|
] = None,
|
||||||
|
) -> Annotated[dict, "A dictionary containing the calendars accessible by the end user"]:
|
||||||
|
"""
|
||||||
|
List all calendars accessible by the user.
|
||||||
|
"""
|
||||||
|
max_results = max(1, min(max_results, 250))
|
||||||
|
service = build_calendar_service(context.get_auth_token_or_empty())
|
||||||
|
calendars = (
|
||||||
|
service.calendarList()
|
||||||
|
.list(
|
||||||
|
pageToken=next_page_token,
|
||||||
|
showDeleted=show_deleted,
|
||||||
|
showHidden=show_hidden,
|
||||||
|
maxResults=max_results,
|
||||||
|
)
|
||||||
|
.execute()
|
||||||
|
)
|
||||||
|
|
||||||
|
items = calendars.get("items", [])
|
||||||
|
keys = ["description", "id", "summary", "timeZone"]
|
||||||
|
relevant_items = [{k: i.get(k) for k in keys if i.get(k)} for i in items]
|
||||||
|
return {
|
||||||
|
"next_page_token": calendars.get("nextPageToken"),
|
||||||
|
"num_calendars": len(relevant_items),
|
||||||
|
"calendars": relevant_items,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@tool(
|
@tool(
|
||||||
requires_auth=Google(
|
requires_auth=Google(
|
||||||
scopes=[
|
scopes=[
|
||||||
|
|
@ -49,15 +96,7 @@ async def create_event(
|
||||||
) -> Annotated[dict, "A dictionary containing the created event details"]:
|
) -> Annotated[dict, "A dictionary containing the created event details"]:
|
||||||
"""Create a new event/meeting/sync/meetup in the specified calendar."""
|
"""Create a new event/meeting/sync/meetup in the specified calendar."""
|
||||||
|
|
||||||
service = build(
|
service = build_calendar_service(context.get_auth_token_or_empty())
|
||||||
"calendar",
|
|
||||||
"v3",
|
|
||||||
credentials=Credentials(
|
|
||||||
context.authorization.token
|
|
||||||
if context.authorization and context.authorization.token
|
|
||||||
else ""
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the calendar's time zone
|
# Get the calendar's time zone
|
||||||
calendar = service.calendars().get(calendarId=calendar_id).execute()
|
calendar = service.calendars().get(calendarId=calendar_id).execute()
|
||||||
|
|
@ -121,15 +160,7 @@ async def list_events(
|
||||||
ending at 10:00 on September 15 would be included, but an
|
ending at 10:00 on September 15 would be included, but an
|
||||||
event starting at 17:00 on September 16 would not be included.
|
event starting at 17:00 on September 16 would not be included.
|
||||||
"""
|
"""
|
||||||
service = build(
|
service = build_calendar_service(context.get_auth_token_or_empty())
|
||||||
"calendar",
|
|
||||||
"v3",
|
|
||||||
credentials=Credentials(
|
|
||||||
context.authorization.token
|
|
||||||
if context.authorization and context.authorization.token
|
|
||||||
else ""
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the calendar's time zone
|
# Get the calendar's time zone
|
||||||
calendar = service.calendars().get(calendarId=calendar_id).execute()
|
calendar = service.calendars().get(calendarId=calendar_id).execute()
|
||||||
|
|
@ -229,15 +260,7 @@ async def update_event(
|
||||||
`updated_start_datetime` and `updated_end_datetime` are
|
`updated_start_datetime` and `updated_end_datetime` are
|
||||||
independent and can be provided separately.
|
independent and can be provided separately.
|
||||||
"""
|
"""
|
||||||
service = build(
|
service = build_calendar_service(context.get_auth_token_or_empty())
|
||||||
"calendar",
|
|
||||||
"v3",
|
|
||||||
credentials=Credentials(
|
|
||||||
context.authorization.token
|
|
||||||
if context.authorization and context.authorization.token
|
|
||||||
else ""
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
calendar = service.calendars().get(calendarId="primary").execute()
|
calendar = service.calendars().get(calendarId="primary").execute()
|
||||||
time_zone = calendar["timeZone"]
|
time_zone = calendar["timeZone"]
|
||||||
|
|
@ -335,15 +358,7 @@ async def delete_event(
|
||||||
] = SendUpdatesOptions.ALL,
|
] = SendUpdatesOptions.ALL,
|
||||||
) -> Annotated[str, "A string containing the deletion confirmation message"]:
|
) -> Annotated[str, "A string containing the deletion confirmation message"]:
|
||||||
"""Delete an event from Google Calendar."""
|
"""Delete an event from Google Calendar."""
|
||||||
service = build(
|
service = build_calendar_service(context.get_auth_token_or_empty())
|
||||||
"calendar",
|
|
||||||
"v3",
|
|
||||||
credentials=Credentials(
|
|
||||||
context.authorization.token
|
|
||||||
if context.authorization and context.authorization.token
|
|
||||||
else ""
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
service.events().delete(
|
service.events().delete(
|
||||||
calendarId=calendar_id, eventId=event_id, sendUpdates=send_updates.value
|
calendarId=calendar_id, eventId=event_id, sendUpdates=send_updates.value
|
||||||
|
|
@ -405,13 +420,10 @@ async def find_time_slots_when_everyone_is_free(
|
||||||
"""
|
"""
|
||||||
Provides time slots when everyone is free within a given date range and time boundaries.
|
Provides time slots when everyone is free within a given date range and time boundaries.
|
||||||
"""
|
"""
|
||||||
credentials = Credentials(
|
|
||||||
context.authorization.token if context.authorization and context.authorization.token else ""
|
|
||||||
)
|
|
||||||
|
|
||||||
# Build google api services
|
# Build google api services
|
||||||
oauth_service = build("oauth2", "v2", credentials=credentials)
|
oauth_service = build_oauth_service(context.get_auth_token_or_empty())
|
||||||
calendar_service = build("calendar", "v3", credentials=credentials)
|
calendar_service = build_calendar_service(context.get_auth_token_or_empty())
|
||||||
|
|
||||||
email_addresses = email_addresses or []
|
email_addresses = email_addresses or []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ from typing import Annotated
|
||||||
|
|
||||||
from arcade.sdk import ToolContext, tool
|
from arcade.sdk import ToolContext, tool
|
||||||
from arcade.sdk.auth import Google
|
from arcade.sdk.auth import Google
|
||||||
from google.oauth2.credentials import Credentials
|
|
||||||
from googleapiclient.discovery import build
|
|
||||||
|
|
||||||
from arcade_google.constants import DEFAULT_SEARCH_CONTACTS_LIMIT
|
from arcade_google.constants import DEFAULT_SEARCH_CONTACTS_LIMIT
|
||||||
from arcade_google.utils import build_people_service, search_contacts
|
from arcade_google.utils import build_people_service, search_contacts
|
||||||
|
|
@ -32,9 +30,7 @@ async def search_contacts_by_email(
|
||||||
"""
|
"""
|
||||||
Search the user's contacts in Google Contacts by email address.
|
Search the user's contacts in Google Contacts by email address.
|
||||||
"""
|
"""
|
||||||
service = build_people_service(
|
service = build_people_service(context.get_auth_token_or_empty())
|
||||||
context.authorization.token if context.authorization and context.authorization.token else ""
|
|
||||||
)
|
|
||||||
# Warm-up the cache before performing search.
|
# Warm-up the cache before performing search.
|
||||||
# TODO: Ideally we should warmup only if this user (or google domain?) hasn't warmed up recently
|
# TODO: Ideally we should warmup only if this user (or google domain?) hasn't warmed up recently
|
||||||
await _warmup_cache(service)
|
await _warmup_cache(service)
|
||||||
|
|
@ -54,9 +50,7 @@ async def search_contacts_by_name(
|
||||||
"""
|
"""
|
||||||
Search the user's contacts in Google Contacts by name.
|
Search the user's contacts in Google Contacts by name.
|
||||||
"""
|
"""
|
||||||
service = build_people_service(
|
service = build_people_service(context.get_auth_token_or_empty())
|
||||||
context.authorization.token if context.authorization and context.authorization.token else ""
|
|
||||||
)
|
|
||||||
# Warm-up the cache before performing search.
|
# Warm-up the cache before performing search.
|
||||||
# TODO: Ideally we should warmup only if this user (or google domain?) hasn't warmed up recently
|
# TODO: Ideally we should warmup only if this user (or google domain?) hasn't warmed up recently
|
||||||
await _warmup_cache(service)
|
await _warmup_cache(service)
|
||||||
|
|
@ -81,15 +75,7 @@ async def create_contact(
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
# Build the People API service
|
# Build the People API service
|
||||||
service = build(
|
service = build_people_service(context.get_auth_token_or_empty())
|
||||||
"people",
|
|
||||||
"v1",
|
|
||||||
credentials=Credentials(
|
|
||||||
context.authorization.token
|
|
||||||
if context.authorization and context.authorization.token
|
|
||||||
else ""
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Construct the person payload with the specified names
|
# Construct the person payload with the specified names
|
||||||
name_body = {"givenName": given_name}
|
name_body = {"givenName": given_name}
|
||||||
|
|
|
||||||
|
|
@ -828,6 +828,24 @@ def merge_intervals(intervals: list[tuple[datetime, datetime]]) -> list[tuple[da
|
||||||
|
|
||||||
|
|
||||||
# Calendar utils
|
# Calendar utils
|
||||||
|
|
||||||
|
|
||||||
|
def build_oauth_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported]
|
||||||
|
"""
|
||||||
|
Build an OAuth2 service object.
|
||||||
|
"""
|
||||||
|
auth_token = auth_token or ""
|
||||||
|
return build("oauth2", "v2", credentials=Credentials(auth_token))
|
||||||
|
|
||||||
|
|
||||||
|
def build_calendar_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported]
|
||||||
|
"""
|
||||||
|
Build a Calendar service object.
|
||||||
|
"""
|
||||||
|
auth_token = auth_token or ""
|
||||||
|
return build("calendar", "v3", credentials=Credentials(auth_token))
|
||||||
|
|
||||||
|
|
||||||
def weekday_to_name(weekday: int) -> str:
|
def weekday_to_name(weekday: int) -> str:
|
||||||
return ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"][weekday]
|
return ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"][weekday]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ from arcade_google.models import EventVisibility, SendUpdatesOptions
|
||||||
from arcade_google.tools.calendar import (
|
from arcade_google.tools.calendar import (
|
||||||
create_event,
|
create_event,
|
||||||
delete_event,
|
delete_event,
|
||||||
|
list_calendars,
|
||||||
list_events,
|
list_events,
|
||||||
update_event,
|
update_event,
|
||||||
)
|
)
|
||||||
|
|
@ -73,6 +74,19 @@ def calendar_eval_suite() -> EvalSuite:
|
||||||
rubric=rubric,
|
rubric=rubric,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Cases for list_calendars
|
||||||
|
suite.add_case(
|
||||||
|
name="List Calendars",
|
||||||
|
user_message=("What calendars do I have?"),
|
||||||
|
expected_tool_calls=[
|
||||||
|
ExpectedToolCall(
|
||||||
|
func=list_calendars,
|
||||||
|
args={},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
critics=[],
|
||||||
|
)
|
||||||
|
|
||||||
# Cases for create_event
|
# Cases for create_event
|
||||||
suite.add_case(
|
suite.add_case(
|
||||||
name="Create calendar event",
|
name="Create calendar event",
|
||||||
|
|
@ -139,7 +153,7 @@ def calendar_eval_suite() -> EvalSuite:
|
||||||
name="Update a calendar event",
|
name="Update a calendar event",
|
||||||
user_message=(
|
user_message=(
|
||||||
"Oh no! I can't make it to the API Test since I have lunch with an old friend at that time. "
|
"Oh no! I can't make it to the API Test since I have lunch with an old friend at that time. "
|
||||||
"Change the meeting my meeting tomorrow at 3pm to 4pm. Let everyone know."
|
"Change my meeting tomorrow at 3pm to 4pm. Let everyone know."
|
||||||
),
|
),
|
||||||
expected_tool_calls=[
|
expected_tool_calls=[
|
||||||
ExpectedToolCall(
|
ExpectedToolCall(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "arcade_google"
|
name = "arcade_google"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
description = "Arcade.dev LLM tools for Google Workspace"
|
description = "Arcade.dev LLM tools for Google Workspace"
|
||||||
authors = ["Arcade <dev@arcade.dev>"]
|
authors = ["Arcade <dev@arcade.dev>"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ from arcade_google.tools.calendar import (
|
||||||
create_event,
|
create_event,
|
||||||
delete_event,
|
delete_event,
|
||||||
find_time_slots_when_everyone_is_free,
|
find_time_slots_when_everyone_is_free,
|
||||||
|
list_calendars,
|
||||||
list_events,
|
list_events,
|
||||||
update_event,
|
update_event,
|
||||||
)
|
)
|
||||||
|
|
@ -24,7 +25,92 @@ def mock_context():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("arcade_google.tools.calendar.build")
|
@patch("arcade_google.tools.calendar.build_calendar_service")
|
||||||
|
async def test_list_calendars(mock_build_calendar_service, mock_context):
|
||||||
|
mock_service = MagicMock()
|
||||||
|
mock_build_calendar_service.return_value = mock_service
|
||||||
|
|
||||||
|
expected_api_response = {
|
||||||
|
"etag": '"p33for2n0pvc8o0o"',
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"accessRole": "reader",
|
||||||
|
"backgroundColor": "#16a765",
|
||||||
|
"colorId": "8",
|
||||||
|
"conferenceProperties": {"allowedConferenceSolutionTypes": ["hangoutsMeet"]},
|
||||||
|
"defaultReminders": [],
|
||||||
|
"description": "Holidays and Observances in Brazil",
|
||||||
|
"etag": '"2347287866334000"',
|
||||||
|
"foregroundColor": "#000000",
|
||||||
|
"id": "en.brazilian#holiday@group.v.calendar.google.com",
|
||||||
|
"kind": "calendar#calendarListEntry",
|
||||||
|
"selected": True,
|
||||||
|
"summary": "Holidays in Brazil",
|
||||||
|
"timeZone": "America/Sao_Paulo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"accessRole": "owner",
|
||||||
|
"backgroundColor": "#9fe1e7",
|
||||||
|
"colorId": "14",
|
||||||
|
"conferenceProperties": {"allowedConferenceSolutionTypes": ["hangoutsMeet"]},
|
||||||
|
"defaultReminders": [{"method": "popup", "minutes": 10}],
|
||||||
|
"etag": '"1743169667849567"',
|
||||||
|
"foregroundColor": "#000000",
|
||||||
|
"id": "example@arcade.dev",
|
||||||
|
"kind": "calendar#calendarListEntry",
|
||||||
|
"notificationSettings": {
|
||||||
|
"notifications": [
|
||||||
|
{"method": "email", "type": "eventCreation"},
|
||||||
|
{"method": "email", "type": "eventChange"},
|
||||||
|
{"method": "email", "type": "eventCancellation"},
|
||||||
|
{"method": "email", "type": "eventResponse"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"primary": True,
|
||||||
|
"selected": True,
|
||||||
|
"summary": "example@arcade.dev",
|
||||||
|
"timeZone": "America/Sao_Paulo",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"kind": "calendar#calendarList",
|
||||||
|
"nextSyncToken": "XkJ8Hy5mN2pQvL9sR4tW7cA3fE1iU6nB",
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_tool_response = {
|
||||||
|
"num_calendars": 2,
|
||||||
|
"calendars": [
|
||||||
|
{
|
||||||
|
"description": "Holidays and Observances in Brazil",
|
||||||
|
"id": "en.brazilian#holiday@group.v.calendar.google.com",
|
||||||
|
"summary": "Holidays in Brazil",
|
||||||
|
"timeZone": "America/Sao_Paulo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "example@arcade.dev",
|
||||||
|
"summary": "example@arcade.dev",
|
||||||
|
"timeZone": "America/Sao_Paulo",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"next_page_token": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_service.calendarList().list().execute.return_value = expected_api_response
|
||||||
|
|
||||||
|
response = await list_calendars(context=mock_context)
|
||||||
|
assert response == expected_tool_response
|
||||||
|
|
||||||
|
# Case: HttpError during calendars listing
|
||||||
|
mock_service.calendarList().list().execute.side_effect = HttpError(
|
||||||
|
resp=MagicMock(status=400),
|
||||||
|
content=b'{"error": {"message": "Invalid request"}}',
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ToolExecutionError):
|
||||||
|
await list_calendars(context=mock_context)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@patch("arcade_google.tools.calendar.build_calendar_service")
|
||||||
async def test_create_event(mock_build, mock_context):
|
async def test_create_event(mock_build, mock_context):
|
||||||
mock_service = MagicMock()
|
mock_service = MagicMock()
|
||||||
mock_build.return_value = mock_service
|
mock_build.return_value = mock_service
|
||||||
|
|
@ -52,7 +138,7 @@ async def test_create_event(mock_build, mock_context):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("arcade_google.tools.calendar.build")
|
@patch("arcade_google.tools.calendar.build_calendar_service")
|
||||||
async def test_list_events(mock_build, mock_context):
|
async def test_list_events(mock_build, mock_context):
|
||||||
mock_service = MagicMock()
|
mock_service = MagicMock()
|
||||||
mock_build.return_value = mock_service
|
mock_build.return_value = mock_service
|
||||||
|
|
@ -117,7 +203,7 @@ async def test_list_events(mock_build, mock_context):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("arcade_google.tools.calendar.build")
|
@patch("arcade_google.tools.calendar.build_calendar_service")
|
||||||
async def test_update_event(mock_build, mock_context):
|
async def test_update_event(mock_build, mock_context):
|
||||||
mock_service = MagicMock()
|
mock_service = MagicMock()
|
||||||
mock_build.return_value = mock_service
|
mock_build.return_value = mock_service
|
||||||
|
|
@ -145,7 +231,7 @@ async def test_update_event(mock_build, mock_context):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("arcade_google.tools.calendar.build")
|
@patch("arcade_google.tools.calendar.build_calendar_service")
|
||||||
async def test_delete_event(mock_build, mock_context):
|
async def test_delete_event(mock_build, mock_context):
|
||||||
mock_service = MagicMock()
|
mock_service = MagicMock()
|
||||||
mock_build.return_value = mock_service
|
mock_build.return_value = mock_service
|
||||||
|
|
@ -164,15 +250,19 @@ async def test_delete_event(mock_build, mock_context):
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("arcade_google.utils.get_now")
|
@patch("arcade_google.utils.get_now")
|
||||||
@patch("arcade_google.tools.calendar.build")
|
@patch("arcade_google.tools.calendar.build_oauth_service")
|
||||||
async def test_find_free_slots_happiest_path_single_user(mock_build, mock_get_now, mock_context):
|
@patch("arcade_google.tools.calendar.build_calendar_service")
|
||||||
|
async def test_find_free_slots_happiest_path_single_user(
|
||||||
|
mock_build_calendar_service, mock_build_oauth_service, mock_get_now, mock_context
|
||||||
|
):
|
||||||
calendar_service = MagicMock()
|
calendar_service = MagicMock()
|
||||||
oauth_service = MagicMock()
|
oauth_service = MagicMock()
|
||||||
|
|
||||||
mock_get_now.return_value = datetime(
|
mock_get_now.return_value = datetime(
|
||||||
2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
|
2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
|
||||||
)
|
)
|
||||||
mock_build.side_effect = [oauth_service, calendar_service]
|
mock_build_oauth_service.return_value = oauth_service
|
||||||
|
mock_build_calendar_service.return_value = calendar_service
|
||||||
|
|
||||||
oauth_service.userinfo().get().execute.return_value = {
|
oauth_service.userinfo().get().execute.return_value = {
|
||||||
"email": "example@arcade.dev",
|
"email": "example@arcade.dev",
|
||||||
|
|
@ -226,9 +316,10 @@ async def test_find_free_slots_happiest_path_single_user(mock_build, mock_get_no
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("arcade_google.utils.get_now")
|
@patch("arcade_google.utils.get_now")
|
||||||
@patch("arcade_google.tools.calendar.build")
|
@patch("arcade_google.tools.calendar.build_oauth_service")
|
||||||
|
@patch("arcade_google.tools.calendar.build_calendar_service")
|
||||||
async def test_find_free_slots_happiest_path_single_user_with_busy_times(
|
async def test_find_free_slots_happiest_path_single_user_with_busy_times(
|
||||||
mock_build, mock_get_now, mock_context
|
mock_build_calendar_service, mock_build_oauth_service, mock_get_now, mock_context
|
||||||
):
|
):
|
||||||
calendar_service = MagicMock()
|
calendar_service = MagicMock()
|
||||||
oauth_service = MagicMock()
|
oauth_service = MagicMock()
|
||||||
|
|
@ -236,7 +327,9 @@ async def test_find_free_slots_happiest_path_single_user_with_busy_times(
|
||||||
mock_get_now.return_value = datetime(
|
mock_get_now.return_value = datetime(
|
||||||
2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
|
2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
|
||||||
)
|
)
|
||||||
mock_build.side_effect = [oauth_service, calendar_service]
|
|
||||||
|
mock_build_oauth_service.return_value = oauth_service
|
||||||
|
mock_build_calendar_service.return_value = calendar_service
|
||||||
|
|
||||||
oauth_service.userinfo().get().execute.return_value = {
|
oauth_service.userinfo().get().execute.return_value = {
|
||||||
"email": "example@arcade.dev",
|
"email": "example@arcade.dev",
|
||||||
|
|
@ -321,9 +414,10 @@ async def test_find_free_slots_happiest_path_single_user_with_busy_times(
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("arcade_google.utils.get_now")
|
@patch("arcade_google.utils.get_now")
|
||||||
@patch("arcade_google.tools.calendar.build")
|
@patch("arcade_google.tools.calendar.build_oauth_service")
|
||||||
|
@patch("arcade_google.tools.calendar.build_calendar_service")
|
||||||
async def test_find_free_slots_happiest_path_multiple_users_with_busy_times(
|
async def test_find_free_slots_happiest_path_multiple_users_with_busy_times(
|
||||||
mock_build, mock_get_now, mock_context
|
mock_build_calendar_service, mock_build_oauth_service, mock_get_now, mock_context
|
||||||
):
|
):
|
||||||
calendar_service = MagicMock()
|
calendar_service = MagicMock()
|
||||||
oauth_service = MagicMock()
|
oauth_service = MagicMock()
|
||||||
|
|
@ -331,7 +425,9 @@ async def test_find_free_slots_happiest_path_multiple_users_with_busy_times(
|
||||||
mock_get_now.return_value = datetime(
|
mock_get_now.return_value = datetime(
|
||||||
2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
|
2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
|
||||||
)
|
)
|
||||||
mock_build.side_effect = [oauth_service, calendar_service]
|
|
||||||
|
mock_build_oauth_service.return_value = oauth_service
|
||||||
|
mock_build_calendar_service.return_value = calendar_service
|
||||||
|
|
||||||
oauth_service.userinfo().get().execute.return_value = {
|
oauth_service.userinfo().get().execute.return_value = {
|
||||||
"email": "example@arcade.dev",
|
"email": "example@arcade.dev",
|
||||||
|
|
@ -428,9 +524,10 @@ async def test_find_free_slots_happiest_path_multiple_users_with_busy_times(
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("arcade_google.utils.get_now")
|
@patch("arcade_google.utils.get_now")
|
||||||
@patch("arcade_google.tools.calendar.build")
|
@patch("arcade_google.tools.calendar.build_oauth_service")
|
||||||
|
@patch("arcade_google.tools.calendar.build_calendar_service")
|
||||||
async def test_find_free_slots_with_google_calendar_error_not_found(
|
async def test_find_free_slots_with_google_calendar_error_not_found(
|
||||||
mock_build, mock_get_now, mock_context
|
mock_build_calendar_service, mock_build_oauth_service, mock_get_now, mock_context
|
||||||
):
|
):
|
||||||
calendar_service = MagicMock()
|
calendar_service = MagicMock()
|
||||||
oauth_service = MagicMock()
|
oauth_service = MagicMock()
|
||||||
|
|
@ -438,7 +535,8 @@ async def test_find_free_slots_with_google_calendar_error_not_found(
|
||||||
mock_get_now.return_value = datetime(
|
mock_get_now.return_value = datetime(
|
||||||
2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
|
2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
|
||||||
)
|
)
|
||||||
mock_build.side_effect = [oauth_service, calendar_service]
|
mock_build_oauth_service.return_value = oauth_service
|
||||||
|
mock_build_calendar_service.return_value = calendar_service
|
||||||
|
|
||||||
oauth_service.userinfo().get().execute.return_value = {
|
oauth_service.userinfo().get().execute.return_value = {
|
||||||
"email": "example@arcade.dev",
|
"email": "example@arcade.dev",
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,9 @@ async def test_create_contact_success(mock_context):
|
||||||
service_mock = MagicMock()
|
service_mock = MagicMock()
|
||||||
service_mock.people.return_value = people_mock
|
service_mock.people.return_value = people_mock
|
||||||
|
|
||||||
with patch("arcade_google.tools.contacts.build", return_value=service_mock) as mock_build:
|
with patch(
|
||||||
|
"arcade_google.tools.contacts.build_people_service", return_value=service_mock
|
||||||
|
) as mock_build:
|
||||||
result = await create_contact(
|
result = await create_contact(
|
||||||
mock_context,
|
mock_context,
|
||||||
given_name="Alice",
|
given_name="Alice",
|
||||||
|
|
@ -63,7 +65,7 @@ async def test_create_contact_success_without_optional(mock_context):
|
||||||
service_mock = MagicMock()
|
service_mock = MagicMock()
|
||||||
service_mock.people.return_value = people_mock
|
service_mock.people.return_value = people_mock
|
||||||
|
|
||||||
with patch("arcade_google.tools.contacts.build", return_value=service_mock):
|
with patch("arcade_google.tools.contacts.build_people_service", return_value=service_mock):
|
||||||
result = await create_contact(mock_context, given_name="Bob", family_name=None, email=None)
|
result = await create_contact(mock_context, given_name="Bob", family_name=None, email=None)
|
||||||
assert "contact" in result
|
assert "contact" in result
|
||||||
assert result["contact"] == created_contact_data
|
assert result["contact"] == created_contact_data
|
||||||
|
|
@ -88,7 +90,7 @@ async def test_create_contact_error(mock_context):
|
||||||
service_mock.people.return_value = people_mock
|
service_mock.people.return_value = people_mock
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch("arcade_google.tools.contacts.build", return_value=service_mock),
|
patch("arcade_google.tools.contacts.build_people_service", return_value=service_mock),
|
||||||
pytest.raises(Exception, match="Error in execution of CreateContact"),
|
pytest.raises(Exception, match="Error in execution of CreateContact"),
|
||||||
):
|
):
|
||||||
await create_contact(mock_context, given_name="Alice", family_name="Doe", email=None)
|
await create_contact(mock_context, given_name="Alice", family_name="Doe", email=None)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue