-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathtest_completion_webhook.py
More file actions
145 lines (117 loc) · 4.58 KB
/
Copy pathtest_completion_webhook.py
File metadata and controls
145 lines (117 loc) · 4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import asyncio
import json
import pytest
from braintrust.test_helpers import has_devserver_installed
def _parse_sse_events(response_text: str) -> list[dict[str, object]]:
events = []
lines = response_text.strip().split("\n")
i = 0
while i < len(lines):
if lines[i].startswith("event: "):
event_type = lines[i][7:].strip()
i += 1
if i < len(lines) and lines[i].startswith("data: "):
raw_data = lines[i][6:].strip()
try:
data = json.loads(raw_data) if raw_data else None
except json.JSONDecodeError:
data = raw_data
events.append({"event": event_type, "data": data})
i += 1
else:
events.append({"event": event_type, "data": None})
else:
i += 1
return events
def test_dispatch_completion_webhook_retries(monkeypatch):
from braintrust.devserver import server as devserver_module
attempts = []
sleep_calls = []
async def fake_send(webhook_url, body, timeout):
attempts.append((webhook_url, body, timeout))
if len(attempts) < 3:
raise RuntimeError("transient")
async def fake_sleep(seconds):
sleep_calls.append(seconds)
monkeypatch.setattr(devserver_module, "_send_completion_webhook_request", fake_send)
monkeypatch.setattr(devserver_module.asyncio, "sleep", fake_sleep)
asyncio.run(
devserver_module.dispatch_completion_webhook(
"https://example.com/webhook",
{"projectName": "my-project", "experimentName": "my-exp"},
attempts=3,
backoff_seconds=(1.0, 2.0, 4.0),
timeout_seconds=10.0,
)
)
assert len(attempts) == 3
assert sleep_calls == [1.0, 2.0]
def test_parse_eval_body_accepts_on_complete_webhook():
from braintrust.devserver.schemas import parse_eval_body
parsed = parse_eval_body(
{
"name": "my-eval",
"on_complete_webhook": "https://example.com/webhook",
}
)
assert parsed["on_complete_webhook"] == "https://example.com/webhook"
@pytest.mark.skipif(not has_devserver_installed(), reason="Devserver dependencies not installed (requires .[cli])")
def test_eval_webhook_failure_non_fatal_for_stream(monkeypatch):
from braintrust import Evaluator
from braintrust.devserver import server as devserver_module
from braintrust.devserver.server import create_app
from braintrust.logger import BraintrustState
from starlette.testclient import TestClient
evaluator = Evaluator(
project_name="test-project",
eval_name="test-eval",
data=lambda: [{"input": "x", "expected": "x"}],
task=lambda input_value, _hooks: input_value,
scores=[],
experiment_name=None,
metadata=None,
)
async def fake_cached_login(**_kwargs):
return BraintrustState()
class FakeSummary:
def as_dict(self):
return {
"project_name": "test-project",
"experiment_name": "test-eval",
"scores": {},
}
class FakeResult:
summary = FakeSummary()
dispatch_calls = []
async def fake_dispatch(webhook_url, summary, **_kwargs):
dispatch_calls.append((webhook_url, summary))
raise RuntimeError("webhook delivery failed")
async def fake_eval_async(*, on_complete, **_kwargs):
await on_complete(FakeSummary())
return FakeResult()
monkeypatch.setattr(devserver_module, "cached_login", fake_cached_login)
monkeypatch.setattr(devserver_module, "dispatch_completion_webhook", fake_dispatch)
monkeypatch.setattr(devserver_module, "EvalAsync", fake_eval_async)
response = TestClient(create_app([evaluator])).post(
"/eval",
headers={
"x-bt-auth-token": "test-api-key",
"x-bt-org-name": "test-org",
"Content-Type": "application/json",
"Accept": "text/event-stream",
},
json={
"name": "test-eval",
"stream": True,
"on_complete_webhook": "https://example.com/webhook",
"data": [{"input": "x", "expected": "x"}],
},
)
assert response.status_code == 200
events = _parse_sse_events(response.text)
event_types = [e["event"] for e in events]
assert "summary" in event_types
assert "done" in event_types
assert len(dispatch_calls) == 1
assert dispatch_calls[0][0] == "https://example.com/webhook"
assert dispatch_calls[0][1]["experimentName"] == "test-eval"