Skip to content

Commit

Permalink
Refactored model validation, timestamp retieval, and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
3coins committed May 26, 2022
1 parent e40e9ca commit 7400481
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 28 deletions.
50 changes: 32 additions & 18 deletions jupyter_server/services/events/handlers.py
Expand Up @@ -4,6 +4,7 @@
"""
import logging
from datetime import datetime
from typing import Any, Dict, Optional, Union

from jupyter_telemetry.eventlog import _skip_message
from pythonjsonlogger import jsonlogger
Expand Down Expand Up @@ -74,6 +75,32 @@ def on_close(self):
self.event_bus.handlers.remove(self.logging_handler)


def validate_model(data: Dict[str, Any]) -> None:
"""Validates for required fields in the JSON request body"""
required_keys = {"schema_name", "version", "event"}
for key in required_keys:
if key not in data:
raise web.HTTPError(400, f"Missing `{key}` in the JSON request body.")


def get_timestamp(data: Dict[str, Any]) -> Optional[datetime]:
"""Parses timestamp from the JSON request body"""
try:
if "timestamp" in data:
timestamp = datetime.strptime(data["timestamp"], "%Y-%m-%dT%H:%M:%S%zZ")
else:
timestamp = None
except Exception as e:
raise web.HTTPError(
400,
"""Failed to parse timestamp from JSON request body,
an ISO format datetime string with UTC offset is expected,
for example, 2022-05-26T13:50:00+05:00Z""",
)

return timestamp


class EventHandler(APIHandler):
"""REST api handler for events"""

Expand All @@ -87,25 +114,12 @@ async def post(self):
raise web.HTTPError(400, "No JSON data provided")

try:
if "timestamp" in payload:
timestamp = datetime.strptime(payload["timestamp"], "%Y-%m-%d %H:%M:%S.%f %z")
else:
timestamp = None

if "schema_name" not in payload:
raise web.HTTPError(400, "'schema_name' missing in JSON data")

if "version" not in payload:
raise web.HTTPError(400, "'version' missing in JSON data")

if "event" not in payload:
raise web.HTTPError(400, "'event' missing in JSON data")

validate_model(payload)
self.event_bus.record_event(
schema_name=payload["schema_name"],
version=payload["version"],
event=payload["event"],
timestamp_override=timestamp,
schema_name=payload.get("schema_name"),
version=payload.get("version"),
event=payload.get("event"),
timestamp_override=get_timestamp(payload),
)
self.set_status(204)
self.finish()
Expand Down
47 changes: 37 additions & 10 deletions tests/services/events/test_api.py
Expand Up @@ -2,6 +2,7 @@
import json
import logging
import pathlib
from datetime import datetime

import pytest
import tornado
Expand Down Expand Up @@ -51,6 +52,17 @@ async def test_subscribe_websocket(jp_ws_fetch, event_bus):


payload_1 = """\
{
"schema_name": "event.mock.jupyter.org/message",
"version": 1,
"event": {
"event_message": "Hello, world!"
},
"timestamp": "2022-05-26T12:50:00+00:00Z"
}
"""

payload_2 = """\
{
"schema_name": "event.mock.jupyter.org/message",
"version": 1,
Expand All @@ -61,20 +73,24 @@ async def test_subscribe_websocket(jp_ws_fetch, event_bus):
"""


async def test_post_event(jp_fetch, eventbus_sink):
@pytest.mark.parametrize("payload", [payload_1, payload_2])
async def test_post_event(jp_fetch, eventbus_sink, payload):
event_bus, sink = eventbus_sink

r = await jp_fetch("api", "events", method="POST", body=payload_1)
r = await jp_fetch("api", "events", method="POST", body=payload)
assert r.code == 204

output = sink.getvalue()
assert output
input = json.loads(payload_1)
input = json.loads(payload)
data = json.loads(output)
assert input["event"]["event_message"] == data["event_message"]
assert data["__timestamp__"]
if "timestamp" in input:
assert input["timestamp"] == data["__timestamp__"]


payload_2 = """\
payload_3 = """\
{
"schema_name": "event.mock.jupyter.org/message",
"event": {
Expand All @@ -83,7 +99,7 @@ async def test_post_event(jp_fetch, eventbus_sink):
}
"""

payload_3 = """\
payload_4 = """\
{
"version": 1,
"event": {
Expand All @@ -92,23 +108,34 @@ async def test_post_event(jp_fetch, eventbus_sink):
}
"""

payload_4 = """\
payload_5 = """\
{
"schema_name": "event.mock.jupyter.org/message",
"version": 1
}
"""

payload_6 = """\
{
"schema_name": "event.mock.jupyter.org/message",
"version": 1,
"event": {
"event_message": "Hello, world!"
},
"timestamp": "2022-05-26 12:50:00"
}
"""

@pytest.mark.parametrize("payload", [payload_2, payload_3, payload_4])

@pytest.mark.parametrize("payload", [payload_3, payload_4, payload_5, payload_6])
async def test_post_event_400(jp_fetch, event_bus, payload):
with pytest.raises(tornado.httpclient.HTTPClientError) as e:
await jp_fetch("api", "events", method="POST", body=payload)

expected_http_error(e, 400)


payload_5 = """\
payload_7 = """\
{
"schema_name": "event.mock.jupyter.org/message",
"version": 1,
Expand All @@ -118,7 +145,7 @@ async def test_post_event_400(jp_fetch, event_bus, payload):
}
"""

payload_6 = """\
payload_8 = """\
{
"schema_name": "event.mock.jupyter.org/message",
"version": 2,
Expand All @@ -129,7 +156,7 @@ async def test_post_event_400(jp_fetch, event_bus, payload):
"""


@pytest.mark.parametrize("payload", [payload_5, payload_6])
@pytest.mark.parametrize("payload", [payload_7, payload_8])
async def test_post_event_500(jp_fetch, event_bus, payload):
with pytest.raises(tornado.httpclient.HTTPClientError) as e:
await jp_fetch("api", "events", method="POST", body=payload)
Expand Down

0 comments on commit 7400481

Please sign in to comment.