mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-21 07:02:25 +02:00
# 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>
488 lines
16 KiB
Python
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
|