Files
langgraph/libs/sdk-py/tests/test_crons_client.py
T
1fb405bd55 feat(sdk-py): add update method for crons client (#6742)
# Description
- Update Python SDK to add patch method to cron client
- This method lets users modify cron attributes (except assistant ID and
thread ID)

---------

Co-authored-by: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com>
2026-02-04 08:23:48 -08:00

488 lines
16 KiB
Python

"""Tests for the crons client."""
from __future__ import annotations
import json
from datetime import datetime, timezone
import httpx
import pytest
from langgraph_sdk.client import (
CronClient,
HttpClient,
SyncCronClient,
SyncHttpClient,
)
def _cron_payload() -> dict[str, object]:
"""Return a mock cron response payload."""
return {
"run_id": "run_123",
"thread_id": "thread_123",
"assistant_id": "asst_123",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-02T00:00:00Z",
"status": "success",
"metadata": {},
"multitask_strategy": "reject",
}
@pytest.mark.asyncio
async def test_async_create_for_thread():
"""Test that CronClient.create_for_thread works without end_time."""
cron = _cron_payload()
async def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/threads/thread_123/runs/crons"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 0 * * *"
assert body["assistant_id"] == "asst_123"
assert "end_time" not in body # Should be filtered out by the None check
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(
transport=transport, base_url="https://example.com"
) as client:
http_client = HttpClient(client)
cron_client = CronClient(http_client)
result = await cron_client.create_for_thread(
thread_id="thread_123",
assistant_id="asst_123",
schedule="0 0 * * *",
)
assert result == cron
@pytest.mark.asyncio
async def test_async_create_for_thread_with_end_time():
"""Test that CronClient.create_for_thread includes end_time in the payload."""
cron = _cron_payload()
end_time = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc)
async def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/threads/thread_123/runs/crons"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 0 * * *"
assert body["assistant_id"] == "asst_123"
assert body["end_time"] == "2025-12-31T23:59:59+00:00"
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(
transport=transport, base_url="https://example.com"
) as client:
http_client = HttpClient(client)
cron_client = CronClient(http_client)
result = await cron_client.create_for_thread(
thread_id="thread_123",
assistant_id="asst_123",
schedule="0 0 * * *",
end_time=end_time,
)
assert result == cron
@pytest.mark.asyncio
async def test_async_create():
"""Test that CronClient.create works without end_time."""
cron = _cron_payload()
async def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/runs/crons"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 12 * * *"
assert body["assistant_id"] == "asst_456"
assert "end_time" not in body # Should be filtered out by the None check
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(
transport=transport, base_url="https://example.com"
) as client:
http_client = HttpClient(client)
cron_client = CronClient(http_client)
result = await cron_client.create(
assistant_id="asst_456",
schedule="0 12 * * *",
)
assert result == cron
@pytest.mark.asyncio
async def test_async_create_with_end_time():
"""Test that CronClient.create includes end_time in the payload."""
cron = _cron_payload()
end_time = datetime(2025, 6, 15, 12, 0, 0, tzinfo=timezone.utc)
async def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/runs/crons"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 12 * * *"
assert body["assistant_id"] == "asst_456"
assert body["end_time"] == "2025-06-15T12:00:00+00:00"
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(
transport=transport, base_url="https://example.com"
) as client:
http_client = HttpClient(client)
cron_client = CronClient(http_client)
result = await cron_client.create(
assistant_id="asst_456",
schedule="0 12 * * *",
end_time=end_time,
)
assert result == cron
def test_sync_create_for_thread():
"""Test that SyncCronClient.create_for_thread works without end_time."""
cron = _cron_payload()
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/threads/thread_123/runs/crons"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 0 * * *"
assert body["assistant_id"] == "asst_123"
assert "end_time" not in body # Should be filtered out by the None check
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport, base_url="https://example.com") as client:
http_client = SyncHttpClient(client)
cron_client = SyncCronClient(http_client)
result = cron_client.create_for_thread(
thread_id="thread_123",
assistant_id="asst_123",
schedule="0 0 * * *",
)
assert result == cron
def test_sync_create_for_thread_with_end_time():
"""Test that SyncCronClient.create_for_thread includes end_time in the payload."""
cron = _cron_payload()
end_time = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc)
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/threads/thread_123/runs/crons"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 0 * * *"
assert body["assistant_id"] == "asst_123"
assert body["end_time"] == "2025-12-31T23:59:59+00:00"
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport, base_url="https://example.com") as client:
http_client = SyncHttpClient(client)
cron_client = SyncCronClient(http_client)
result = cron_client.create_for_thread(
thread_id="thread_123",
assistant_id="asst_123",
schedule="0 0 * * *",
end_time=end_time,
)
assert result == cron
def test_sync_create():
"""Test that SyncCronClient.create works without end_time."""
cron = _cron_payload()
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/runs/crons"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 12 * * *"
assert body["assistant_id"] == "asst_456"
assert "end_time" not in body # Should be filtered out by the None check
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport, base_url="https://example.com") as client:
http_client = SyncHttpClient(client)
cron_client = SyncCronClient(http_client)
result = cron_client.create(
assistant_id="asst_456",
schedule="0 12 * * *",
)
assert result == cron
def test_sync_create_with_end_time():
"""Test that SyncCronClient.create includes end_time in the payload."""
cron = _cron_payload()
end_time = datetime(2025, 6, 15, 12, 0, 0, tzinfo=timezone.utc)
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/runs/crons"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 12 * * *"
assert body["assistant_id"] == "asst_456"
assert body["end_time"] == "2025-06-15T12:00:00+00:00"
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport, base_url="https://example.com") as client:
http_client = SyncHttpClient(client)
cron_client = SyncCronClient(http_client)
result = cron_client.create(
assistant_id="asst_456",
schedule="0 12 * * *",
end_time=end_time,
)
assert result == cron
@pytest.mark.parametrize(
"enabled_value",
[True, False],
ids=["enabled", "disabled"],
)
def test_sync_create_with_enabled_parameter(enabled_value):
"""Test that SyncCronClient.create includes enabled parameter in the payload."""
cron = _cron_payload()
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/runs/crons"
body = json.loads(request.content)
assert body["schedule"] == "0 12 * * *"
assert body["assistant_id"] == "asst_456"
assert body["enabled"] == enabled_value
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport, base_url="https://example.com") as client:
http_client = SyncHttpClient(client)
cron_client = SyncCronClient(http_client)
result = cron_client.create(
assistant_id="asst_456",
schedule="0 12 * * *",
enabled=enabled_value,
)
assert result == cron
def _cron_response() -> dict[str, object]:
"""Return a mock Cron object response."""
return {
"cron_id": "cron_123",
"assistant_id": "asst_123",
"thread_id": "thread_123",
"on_run_completed": None,
"end_time": "2025-12-31T23:59:59+00:00",
"schedule": "0 10 * * *",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-02T00:00:00Z",
"payload": {},
"user_id": None,
"next_run_date": "2024-01-03T10:00:00Z",
"metadata": {},
"enabled": True,
}
@pytest.mark.asyncio
async def test_async_update():
"""Test that CronClient.update works with schedule and enabled parameters."""
cron = _cron_response()
async def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "PATCH"
assert request.url.path == "/runs/crons/cron_123"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 10 * * *"
assert body["enabled"] is False
assert "end_time" not in body # Should be filtered out by the None check
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(
transport=transport, base_url="https://example.com"
) as client:
http_client = HttpClient(client)
cron_client = CronClient(http_client)
result = await cron_client.update(
cron_id="cron_123",
schedule="0 10 * * *",
enabled=False,
)
assert result == cron
@pytest.mark.asyncio
async def test_async_update_with_end_time():
"""Test that CronClient.update includes end_time in the payload."""
cron = _cron_response()
end_time = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc)
async def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "PATCH"
assert request.url.path == "/runs/crons/cron_123"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 10 * * *"
assert body["end_time"] == "2025-12-31T23:59:59+00:00"
assert body["enabled"] is True
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(
transport=transport, base_url="https://example.com"
) as client:
http_client = HttpClient(client)
cron_client = CronClient(http_client)
result = await cron_client.update(
cron_id="cron_123",
schedule="0 10 * * *",
end_time=end_time,
enabled=True,
)
assert result == cron
def test_sync_update():
"""Test that SyncCronClient.update works with schedule and enabled parameters."""
cron = _cron_response()
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "PATCH"
assert request.url.path == "/runs/crons/cron_123"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 10 * * *"
assert body["enabled"] is False
assert "end_time" not in body # Should be filtered out by the None check
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport, base_url="https://example.com") as client:
http_client = SyncHttpClient(client)
cron_client = SyncCronClient(http_client)
result = cron_client.update(
cron_id="cron_123",
schedule="0 10 * * *",
enabled=False,
)
assert result == cron
def test_sync_update_with_end_time():
"""Test that SyncCronClient.update includes end_time in the payload."""
cron = _cron_response()
end_time = datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc)
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "PATCH"
assert request.url.path == "/runs/crons/cron_123"
# Parse the request body
body = json.loads(request.content)
assert body["schedule"] == "0 10 * * *"
assert body["end_time"] == "2025-12-31T23:59:59+00:00"
assert body["enabled"] is True
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport, base_url="https://example.com") as client:
http_client = SyncHttpClient(client)
cron_client = SyncCronClient(http_client)
result = cron_client.update(
cron_id="cron_123",
schedule="0 10 * * *",
end_time=end_time,
enabled=True,
)
assert result == cron
@pytest.mark.parametrize(
"enabled_value",
[True, False],
ids=["enabled", "disabled"],
)
def test_sync_update_with_enabled_parameter(enabled_value):
"""Test that SyncCronClient.update includes enabled parameter in the payload."""
cron = _cron_response()
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "PATCH"
assert request.url.path == "/runs/crons/cron_456"
body = json.loads(request.content)
assert body["enabled"] == enabled_value
assert "schedule" not in body # Only enabled is set
return httpx.Response(200, json=cron)
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport, base_url="https://example.com") as client:
http_client = SyncHttpClient(client)
cron_client = SyncCronClient(http_client)
result = cron_client.update(
cron_id="cron_456",
enabled=enabled_value,
)
assert result == cron