diff --git a/Dockerfile b/Dockerfile index 3e786271605a636c736114929a1f2c9ec78d1bad..a0683c8ffef1ce2bab786e592ad3de3bfcfd55e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,4 +23,4 @@ RUN poetry install --only-root EXPOSE 8080 -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080", "--proxy-headers"] \ No newline at end of file +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080", "--proxy-headers", "--no-access-log"] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 9d44e549fc9d88176b34db4b9b802548b9b4322f..10b93b4b55614474dea2e3b0cbc03451caee223b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "zoom-python-webhook" -version = "0.1.3" +version = "0.2.0" description = "Fast API app for Zoom Webhook" authors = ["Samuel Guillemet <samuel.guillemet@telecom-sudparis.eu>"] readme = "README.md" diff --git a/src/app/components/zoom_room_checked_in/schema.py b/src/app/components/zoom_room_checked_in/schema.py index cdad9b398efb8e5f24f0eab2c2553169c4060846..cc209353d57e42871057706e01cf7e146496c81e 100644 --- a/src/app/components/zoom_room_checked_in/schema.py +++ b/src/app/components/zoom_room_checked_in/schema.py @@ -38,7 +38,7 @@ class CheckedInWebHook(BaseWebhookEvent): payload: Payload = Field( ..., - description="Contains a property with the plainToken value, the string to hash.", + description="Contains all the information about the Zoom Room that checked in.", ) diff --git a/src/app/components/zoom_room_checked_out/schema.py b/src/app/components/zoom_room_checked_out/schema.py index 66f6bc08abaddd6ee3e541e72303f7f5db580c5e..cb6393397dfa423f2a04a673c960cc20639ea266 100644 --- a/src/app/components/zoom_room_checked_out/schema.py +++ b/src/app/components/zoom_room_checked_out/schema.py @@ -30,7 +30,7 @@ class Payload(BaseModel): class CheckedOutWebHook(BaseWebhookEvent): - """This is the schema for the request body of the webhook for the zoomroom.checked_in event. + """This is the schema for the request body of the webhook for the zoomroom.checked_out event. Args: WebhookEvent (WebhookEvent): The base webhook model for the schema. @@ -38,7 +38,7 @@ class CheckedOutWebHook(BaseWebhookEvent): payload: Payload = Field( ..., - description="Contains a property with the plainToken value, the string to hash.", + description="Contains all the information about the Zoom Room that checked out.", ) diff --git a/src/app/components/zoom_room_sensor_data/__init__.py b/src/app/components/zoom_room_sensor_data/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..260006335fe65bf31e1cb27845f04d4f8c2eb91c --- /dev/null +++ b/src/app/components/zoom_room_sensor_data/__init__.py @@ -0,0 +1,8 @@ +""" Zoom Room Sensor Data Component""" + +from .handler import sensor_data_handler +from .schema import ResponseWebhookSensorData + +event_name = "zoomroom.sensor_data" +handler_function = sensor_data_handler +response_model = ResponseWebhookSensorData diff --git a/src/app/components/zoom_room_sensor_data/handler.py b/src/app/components/zoom_room_sensor_data/handler.py new file mode 100644 index 0000000000000000000000000000000000000000..5618320cd1b446d264f8af3afb456f0b1a6af36a --- /dev/null +++ b/src/app/components/zoom_room_sensor_data/handler.py @@ -0,0 +1,38 @@ +import logging + +from .schema import ResponseWebhookSensorData, SensorDataWebHook + +logger = logging.getLogger("app.components.zoomroom.sensor_data") + + +def sensor_data_handler(body: dict): + sensor_data = SensorDataWebHook(**body) + + logger.debug( + "New sensor data event in room %s [%s].", + sensor_data.payload.object.room_name, + sensor_data.payload.object.id, + ) + + try: + sensor_data.payload.object.sensor_data[0] + except IndexError: + logger.warning("No sensor data in the payload.") + return ResponseWebhookSensorData(message="No sensor data in the payload.") + + # For the sensor data: + logger.info( + "%s | %s | %s | %s | %s | %s | %s | %s", + sensor_data.event, + sensor_data.event_ts, + sensor_data.payload.account_id, + sensor_data.payload.object.id, + sensor_data.payload.object.room_name, + sensor_data.payload.object.sensor_data[0].sensor_type.value, + sensor_data.payload.object.sensor_data[0].sensor_value, + sensor_data.payload.object.sensor_data[0].date_time, + ) + + # TODO: Add code here to handle the sensor_data event. + + return ResponseWebhookSensorData(message="Sensor data event received.") diff --git a/src/app/components/zoom_room_sensor_data/schema.py b/src/app/components/zoom_room_sensor_data/schema.py new file mode 100644 index 0000000000000000000000000000000000000000..5a843a5984e319b272705ec220db1ec132751dc6 --- /dev/null +++ b/src/app/components/zoom_room_sensor_data/schema.py @@ -0,0 +1,63 @@ +from datetime import datetime +from enum import Enum +from typing import List + +from pydantic import BaseModel, Field + +from app.core.base_webhook_event_schema import ( + BaseResponseWebhookEvent, + BaseWebhookEvent, +) + + +class SensorType(Enum): + CO2 = "CO2" + TEMPERATURE = "TEMPERATURE" + REAL_TIME_PEOPLE_COUNT = "REAL_TIME_PEOPLE_COUNT" + HUMIDITY = "HUMIDITY" + VOC = "VOC" + + +class SensorData(BaseModel): + date_time: datetime = Field( + ..., description="The time when the sensor data was reported." + ) + sensor_type: SensorType = Field(..., description="The type of sensor.") + sensor_value: str = Field(..., description="The value of the sensor.") + + +class Object(BaseModel): + id: str = Field(..., description="The ID of the Zoom Room.") + room_name: str = Field(..., description="The name of the Zoom Room.") + device_id: str = Field(..., description="The ID of the device.") + sensor_data: List[SensorData] = Field( + ..., description="The sensor data reported by the device." + ) + + +class Payload(BaseModel): + account_id: str = Field(..., description="The account ID of the Zoom account.") + object: Object = Field(..., description="Information about the Zoom Room.") + + +class SensorDataWebHook(BaseWebhookEvent): + """This is the schema for the request body of the webhook for the zoomroom.sensor_data event. + + Args: + WebhookEvent (WebhookEvent): The base webhook model for the schema. + """ + + payload: Payload = Field( + ..., + description="Contains all the information about the Zoom Room that reported the sensor data.", + ) + + +class ResponseWebhookSensorData(BaseResponseWebhookEvent): + """This is the schema for the response for zoomroom.sensor_data webhook. + + Args: + BaseResponseWebhookEvent (BaseResponseWebhookEvent): The base model for the schema. + """ + + message: str = Field(..., description="The message of the response.") diff --git a/tests/app/api/v1/endpoints/test_webhook.py b/tests/app/api/v1/endpoints/test_webhook.py index 1da36301c71bb7b58d3b8b54e05cbd3609f4cbcb..ca0e9cf4b9e17dbd2b59fff93f13fdbf9fba2057 100644 --- a/tests/app/api/v1/endpoints/test_webhook.py +++ b/tests/app/api/v1/endpoints/test_webhook.py @@ -10,60 +10,7 @@ async def override_verify_webhook_signature(): pass -url_validation_body = { - "payload": {"plainToken": "q9ibPhGeRZ6ayx5WTrXjRw"}, - "event_ts": 1689061099652, - "event": "endpoint.url_validation", -} - -checked_in_body = { - "event": "zoomroom.checked_in", - "event_ts": 1626230691572, - "payload": { - "account_id": "AAAAAABBBB", - "object": { - "id": "abcD3ojfdbjfg", - "room_name": "My Zoom Room", - "calendar_name": "mycalendar@example.com", - "email": "jchill@example.com", - "event_id": "AbbbbbGYxLTc3OTVkMzFmZDc0MwBGAAAAAAD48FI58voYSqDgJePOSZ", - "change_key": "DwAAABYAAABQ/N0JvB/FRqv5UT2rFfkVAAE2XqVw", - "resource_email": "zroom1@example.com", - "calendar_id": "mycalendar@example.com", - "calendar_type": "2", - "api_type": "0", - }, - }, -} - -checked_out_body = { - "event": "zoomroom.checked_out", - "event_ts": 1626230691572, - "payload": { - "account_id": "AAAAAABBBB", - "object": { - "id": "abcD3ojfdbjfg", - "room_name": "My Zoom Room", - "calendar_name": "mycalendar@example.com", - "email": "jchill@example.com", - "event_id": "AbbbbbGYxLTc3OTVkMzFmZDc0MwBGAAAAAAD48FI58voYSqDgJePOSZ", - "change_key": "DwAAABYAAABQ/N0JvB/FRqv5UT2rFfkVAAE2XqVw", - "resource_email": "zroom1@example.com", - "calendar_id": "mycalendar@example.com", - "calendar_type": "2", - "api_type": "0", - }, - }, -} - -not_supported_event_body = { - "event": "not_supported_event", - "event_ts": 1626230691572, - "payload": {}, -} - - -def test_url_validation(): +def test_url_validation(url_validation_body): app.dependency_overrides[ verify_webhook_signature ] = override_verify_webhook_signature @@ -75,7 +22,7 @@ def test_url_validation(): assert response.status_code == 200 -def test_check_in(): +def test_check_in(checked_in_body): app.dependency_overrides[ verify_webhook_signature ] = override_verify_webhook_signature @@ -87,7 +34,7 @@ def test_check_in(): assert response.status_code == 200 -def test_check_out(): +def test_check_out(checked_out_body): app.dependency_overrides[ verify_webhook_signature ] = override_verify_webhook_signature @@ -99,7 +46,19 @@ def test_check_out(): assert response.status_code == 200 -def test_not_supported_event(): +def test_sensor_data(sensor_data_body): + app.dependency_overrides[ + verify_webhook_signature + ] = override_verify_webhook_signature + response = client.post( + "api/v1/webhook", + json=sensor_data_body, + ) + + assert response.status_code == 200 + + +def test_not_supported_event(not_supported_event_body): app.dependency_overrides[ verify_webhook_signature ] = override_verify_webhook_signature diff --git a/tests/app/components/endpoint_url_validation/test_url_validation_handler.py b/tests/app/components/endpoint_url_validation/test_url_validation_handler.py index 540b3e6827f367fff3d9debb3bdda6e0a4645b5f..74da757a91596d0019f6ec83fea5dca788ec6e61 100644 --- a/tests/app/components/endpoint_url_validation/test_url_validation_handler.py +++ b/tests/app/components/endpoint_url_validation/test_url_validation_handler.py @@ -3,22 +3,16 @@ from pydantic import ValidationError from app.components.endpoint_url_validation import handler_function -body = { - "payload": {"plainToken": "plain_token"}, - "event_ts": 1689061099652, - "event": "endpoint.url_validation", -} - -def test_url_validation_handler(): - assert handler_function(body).model_dump() == { - "plainToken": "plain_token", - "encryptedToken": "6444317b153180cf15822bec8a313bda9c41e3b6a5f009785e8b833ff65010fc", +def test_url_validation_handler(url_validation_body): + assert handler_function(url_validation_body).model_dump() == { + "plainToken": "q9ibPhGeRZ6ayx5WTrXjRw", + "encryptedToken": "7172d1f047a422bd6e327228425bc62c46677352a96c34324be07301ca45a319", } -def test_url_validation_handler_invalid_payload(): - body["payload"] = {} +def test_url_validation_handler_invalid_payload(url_validation_body): + url_validation_body["payload"] = {} with pytest.raises(ValidationError): - handler_function(body) + handler_function(url_validation_body) diff --git a/tests/app/components/zoom_room_checked_in/test_checked_in_handler.py b/tests/app/components/zoom_room_checked_in/test_checked_in_handler.py index b549f46393e7d3fac2fa1316342f4a4c41c8a876..afc526d847c929d1618efc7a852daf5633837b06 100644 --- a/tests/app/components/zoom_room_checked_in/test_checked_in_handler.py +++ b/tests/app/components/zoom_room_checked_in/test_checked_in_handler.py @@ -3,33 +3,13 @@ from pydantic import ValidationError from app.components.zoom_room_checked_in import handler_function -body = { - "event": "zoomroom.checked_in", - "event_ts": 1626230691572, - "payload": { - "account_id": "AAAAAABBBB", - "object": { - "id": "abcD3ojfdbjfg", - "room_name": "My Zoom Room", - "calendar_name": "mycalendar@example.com", - "email": "jchill@example.com", - "event_id": "AbbbbbGYxLTc3OTVkMzFmZDc0MwBGAAAAAAD48FI58voYSqDgJePOSZ", - "change_key": "DwAAABYAAABQ/N0JvB/FRqv5UT2rFfkVAAE2XqVw", - "resource_email": "zroom1@example.com", - "calendar_id": "mycalendar@example.com", - "calendar_type": "2", - "api_type": "0", - }, - }, -} +def test_checked_in_handler(checked_in_body): + assert handler_function(checked_in_body).model_dump() == {"message": "Checked in!"} -def test_checked_in_handler(): - assert handler_function(body).model_dump() == {"message": "Checked in!"} - -def test_checked_in_handler_invalid_payload(): - body["payload"] = {} +def test_checked_in_handler_invalid_payload(checked_in_body): + checked_in_body["payload"] = {} with pytest.raises(ValidationError): - handler_function(body) + handler_function(checked_in_body) diff --git a/tests/app/components/zoom_room_checked_out/test_checked_out_handler.py b/tests/app/components/zoom_room_checked_out/test_checked_out_handler.py index b318cd3d1babdcf7537fd6e74c91e513578426f3..0743da21f29a3c7cbf1927ca9247fce3bb720069 100644 --- a/tests/app/components/zoom_room_checked_out/test_checked_out_handler.py +++ b/tests/app/components/zoom_room_checked_out/test_checked_out_handler.py @@ -3,33 +3,15 @@ from pydantic import ValidationError from app.components.zoom_room_checked_out import handler_function -body = { - "event": "zoomroom.checked_out", - "event_ts": 1626230691572, - "payload": { - "account_id": "AAAAAABBBB", - "object": { - "id": "abcD3ojfdbjfg", - "room_name": "My Zoom Room", - "calendar_name": "mycalendar@example.com", - "email": "jchill@example.com", - "event_id": "AbbbbbGYxLTc3OTVkMzFmZDc0MwBGAAAAAAD48FI58voYSqDgJePOSZ", - "change_key": "DwAAABYAAABQ/N0JvB/FRqv5UT2rFfkVAAE2XqVw", - "resource_email": "zroom1@example.com", - "calendar_id": "mycalendar@example.com", - "calendar_type": "2", - "api_type": "0", - }, - }, -} +def test_checked_out_handler(checked_out_body): + assert handler_function(checked_out_body).model_dump() == { + "message": "Checked out!" + } -def test_checked_out_handler(): - assert handler_function(body).model_dump() == {"message": "Checked out!"} - -def test_checked_out_handler_invalid_payload(): - body["payload"] = {} +def test_checked_out_handler_invalid_payload(checked_out_body): + checked_out_body["payload"] = {} with pytest.raises(ValidationError): - handler_function(body) + handler_function(checked_out_body) diff --git a/tests/app/components/zoom_room_sensor_data/__init__.py b/tests/app/components/zoom_room_sensor_data/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/app/components/zoom_room_sensor_data/test_sensor_data_handler.py b/tests/app/components/zoom_room_sensor_data/test_sensor_data_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..2d7ef03fdd264859be1c7642ef40a98733957f5b --- /dev/null +++ b/tests/app/components/zoom_room_sensor_data/test_sensor_data_handler.py @@ -0,0 +1,24 @@ +import pytest +from pydantic import ValidationError + +from app.components.zoom_room_sensor_data import handler_function + + +def test_sensor_data_handler(sensor_data_body): + assert handler_function(sensor_data_body).model_dump() == { + "message": "Sensor data event received." + } + + +def test_sensor_data_handler_no_data(sensor_data_body): + sensor_data_body["payload"]["object"]["sensor_data"] = [] + assert handler_function(sensor_data_body).model_dump() == { + "message": "No sensor data in the payload." + } + + +def test_sensor_data_handler_invalid_payload(sensor_data_body): + sensor_data_body["payload"] = {} + + with pytest.raises(ValidationError): + handler_function(sensor_data_body) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..495076a5bc69dc2afc8b38fd9fd3d158138d9967 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,88 @@ +import pytest + + +@pytest.fixture +def url_validation_body(): + return { + "payload": {"plainToken": "q9ibPhGeRZ6ayx5WTrXjRw"}, + "event_ts": 1689061099652, + "event": "endpoint.url_validation", + } + + +@pytest.fixture +def checked_in_body(): + return { + "event": "zoomroom.checked_in", + "event_ts": 1626230691572, + "payload": { + "account_id": "AAAAAABBBB", + "object": { + "id": "abcD3ojfdbjfg", + "room_name": "My Zoom Room", + "calendar_name": "mycalendar@example.com", + "email": "jchill@example.com", + "event_id": "AbbbbbGYxLTc3OTVkMzFmZDc0MwBGAAAAAAD48FI58voYSqDgJePOSZ", + "change_key": "DwAAABYAAABQ/N0JvB/FRqv5UT2rFfkVAAE2XqVw", + "resource_email": "zroom1@example.com", + "calendar_id": "mycalendar@example.com", + "calendar_type": "2", + "api_type": "0", + }, + }, + } + + +@pytest.fixture +def checked_out_body(): + return { + "event": "zoomroom.checked_out", + "event_ts": 1626230691572, + "payload": { + "account_id": "AAAAAABBBB", + "object": { + "id": "abcD3ojfdbjfg", + "room_name": "My Zoom Room", + "calendar_name": "mycalendar@example.com", + "email": "jchill@example.com", + "event_id": "AbbbbbGYxLTc3OTVkMzFmZDc0MwBGAAAAAAD48FI58voYSqDgJePOSZ", + "change_key": "DwAAABYAAABQ/N0JvB/FRqv5UT2rFfkVAAE2XqVw", + "resource_email": "zroom1@example.com", + "calendar_id": "mycalendar@example.com", + "calendar_type": "2", + "api_type": "0", + }, + }, + } + + +@pytest.fixture +def sensor_data_body(): + return { + "event": "zoomroom.sensor_data", + "event_ts": 1626230691572, + "payload": { + "account_id": "AAAAAABBBB", + "object": { + "id": "abcD3ojfdbjfg", + "room_name": "My Zoom Room", + "device_id": "NiVvY1NWpE2nulNrhVjgU4jD0swziVrXBbaFZyC3u+o=", + "sensor_data": [ + { + "date_time": "2022-06-19T00:00:00Z", + "sensor_type": "REAL_TIME_PEOPLE_COUNT", + "sensor_value": "20", + } + ], + }, + }, + } + + +@pytest.fixture +def not_supported_event_body(): + return { + "event": "not_supported_event", + "event_ts": 1626230691572, + "payload": {}, + }