|
10 | 10 |
|
11 | 11 | import openai |
12 | 12 | from openai._exceptions import InvalidWebhookSignatureError |
13 | | -from openai.types.webhooks import UnwrapWebhookEvent |
| 13 | +from openai.types.webhooks import ( |
| 14 | + UnwrapWebhookEvent, |
| 15 | + SafetyWarningIssuedWebhookEvent, |
| 16 | + SafetyDeactivationIssuedWebhookEvent, |
| 17 | +) |
14 | 18 |
|
15 | 19 | base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") |
16 | 20 |
|
|
22 | 26 | TEST_SIGNATURE = "v1,gUAg4R2hWouRZqRQG4uJypNS8YK885G838+EHb4nKBY=" |
23 | 27 | SIP_EVENT_TYPES = ["live.call.incoming", "live.transport.incoming", "realtime.call.incoming"] |
24 | 28 | SIP_MEDIA_SECURITY_VALUES = [None, "rtp", "srtp", "future_media_security"] |
| 29 | +SAFETY_EVENT_TYPES = { |
| 30 | + "safety.warning_issued": SafetyWarningIssuedWebhookEvent, |
| 31 | + "safety.deactivation_issued": SafetyDeactivationIssuedWebhookEvent, |
| 32 | +} |
25 | 33 |
|
26 | 34 |
|
27 | 35 | def create_test_headers( |
@@ -66,9 +74,49 @@ def assert_sip_event(event: UnwrapWebhookEvent, event_type: str, media_security: |
66 | 74 | assert event.to_dict() == json.loads(payload) |
67 | 75 |
|
68 | 76 |
|
| 77 | +def create_safety_test_payload(event_type: str) -> tuple[str, dict[str, str]]: |
| 78 | + payload = json.dumps( |
| 79 | + { |
| 80 | + "id": "evt_safety_test", |
| 81 | + "object": "event", |
| 82 | + "created_at": TEST_TIMESTAMP, |
| 83 | + "type": event_type, |
| 84 | + "data": {"id": "case_test"}, |
| 85 | + } |
| 86 | + ) |
| 87 | + signed_payload = f"{TEST_WEBHOOK_ID}.{TEST_TIMESTAMP}.{payload}".encode() |
| 88 | + signature = base64.b64encode( |
| 89 | + hmac.new(base64.b64decode(TEST_SECRET.removeprefix("whsec_")), signed_payload, "sha256").digest() |
| 90 | + ).decode() |
| 91 | + return payload, create_test_headers(signature=f"v1,{signature}") |
| 92 | + |
| 93 | + |
| 94 | +def assert_safety_event(event: UnwrapWebhookEvent, event_type: str, payload: str) -> None: |
| 95 | + assert isinstance(event, SAFETY_EVENT_TYPES[event_type]) |
| 96 | + assert event.type == "safety.warning_issued" or event.type == "safety.deactivation_issued" |
| 97 | + assert event.type == event_type |
| 98 | + assert event.data.id == "case_test" |
| 99 | + assert event.to_dict() == json.loads(payload) |
| 100 | + |
| 101 | + |
69 | 102 | class TestWebhooks: |
70 | 103 | parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) |
71 | 104 |
|
| 105 | + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) |
| 106 | + @parametrize |
| 107 | + @pytest.mark.parametrize("event_type", SAFETY_EVENT_TYPES) |
| 108 | + def test_unwrap_safety_events(self, client: openai.OpenAI, event_type: str) -> None: |
| 109 | + payload, headers = create_safety_test_payload(event_type) |
| 110 | + event = client.webhooks.unwrap(payload, headers, secret=TEST_SECRET) |
| 111 | + assert_safety_event(event, event_type, payload) |
| 112 | + |
| 113 | + for tampered_payload in [payload.replace("case_test", "case_tampered"), "{"]: |
| 114 | + with pytest.raises(InvalidWebhookSignatureError, match="The given webhook signature does not match"): |
| 115 | + client.webhooks.unwrap(tampered_payload, headers, secret=TEST_SECRET) |
| 116 | + |
| 117 | + with pytest.raises(InvalidWebhookSignatureError, match="The given webhook signature does not match"): |
| 118 | + client.webhooks.unwrap(payload, headers, secret="wrong_safety_webhook_secret") |
| 119 | + |
72 | 120 | @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) |
73 | 121 | @parametrize |
74 | 122 | @pytest.mark.parametrize("event_type", SIP_EVENT_TYPES) |
@@ -220,6 +268,21 @@ class TestAsyncWebhooks: |
220 | 268 | "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] |
221 | 269 | ) |
222 | 270 |
|
| 271 | + @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) |
| 272 | + @parametrize |
| 273 | + @pytest.mark.parametrize("event_type", SAFETY_EVENT_TYPES) |
| 274 | + async def test_unwrap_safety_events(self, async_client: openai.AsyncOpenAI, event_type: str) -> None: |
| 275 | + payload, headers = create_safety_test_payload(event_type) |
| 276 | + event = async_client.webhooks.unwrap(payload, headers, secret=TEST_SECRET) |
| 277 | + assert_safety_event(event, event_type, payload) |
| 278 | + |
| 279 | + for tampered_payload in [payload.replace("case_test", "case_tampered"), "{"]: |
| 280 | + with pytest.raises(InvalidWebhookSignatureError, match="The given webhook signature does not match"): |
| 281 | + async_client.webhooks.unwrap(tampered_payload, headers, secret=TEST_SECRET) |
| 282 | + |
| 283 | + with pytest.raises(InvalidWebhookSignatureError, match="The given webhook signature does not match"): |
| 284 | + async_client.webhooks.unwrap(payload, headers, secret="wrong_safety_webhook_secret") |
| 285 | + |
223 | 286 | @mock.patch("time.time", mock.MagicMock(return_value=TEST_TIMESTAMP)) |
224 | 287 | @parametrize |
225 | 288 | @pytest.mark.parametrize("event_type", SIP_EVENT_TYPES) |
|
0 commit comments