예제 #1
0
def test_zoom_api_request_missing_creds():
    utils.common.APIGEE_KEY = None

    # (ZOOM_API_KEY, ZOOM_API_SECRET)
    cases = [(None, None), ("key", None), (None, "secret")]

    for key, secret in cases:
        utils.common.ZOOM_API_KEY = key
        utils.common.ZOOM_API_SECRET = secret
        with pytest.raises(Exception) as exc_info:
            utils.zoom_api_request("meetings")
        assert exc_info.match("Missing api credentials.")
예제 #2
0
def test_apigee_key(caplog):
    utils.common.APIGEE_KEY = "apigee_key"
    utils.common.ZOOM_API_KEY = None
    utils.common.ZOOM_API_SECRET = None
    caplog.set_level(logging.INFO)
    utils.zoom_api_request("meetings")
    assert "apigee request" in caplog.text.lower()

    # Should still use apigee key even if zoom api key/secret defined
    utils.common.ZOOM_API_KEY = "key"
    utils.common.ZOOM_API_SECRET = "secret"
    utils.zoom_api_request("meetings")
    assert "apigee request" in caplog.text.lower()
예제 #3
0
def test_zoom_api_key(caplog):
    utils.common.APIGEE_KEY = None
    utils.common.ZOOM_API_KEY = "key"
    utils.common.ZOOM_API_SECRET = "secret"
    caplog.set_level(logging.INFO)
    utils.zoom_api_request("meetings")
    assert "zoom api request" in caplog.text.lower()

    utils.common.APIGEE_KEY = ""
    utils.common.ZOOM_API_KEY = "key"
    utils.common.ZOOM_API_SECRET = "secret"
    caplog.set_level(logging.INFO)
    utils.zoom_api_request("meetings")
    assert "zoom api request" in caplog.text.lower()
예제 #4
0
def test_url_construction(caplog):
    utils.common.APIGEE_KEY = None
    utils.common.ZOOM_API_KEY = "key"
    utils.common.ZOOM_API_SECRET = "secret"
    caplog.set_level(logging.INFO)

    cases = [
        ("https://www.foo.com", "meetings", "https://www.foo.com/meetings"),
        ("https://www.foo.com/", "meetings", "https://www.foo.com/meetings"),
        ("https://www.foo.com/", "/meetings", "https://www.foo.com/meetings"),
    ]
    with requests_mock.mock() as req_mock:
        req_mock.get(requests_mock.ANY,
                     status_code=200,
                     json={"mock_payload": 123})
        for url, endpoint, expected in cases:
            utils.common.ZOOM_API_BASE_URL = url
            utils.zoom_api_request(endpoint)
            assert ("zoom api request to https://www.foo.com/meetings"
                    in caplog.text.lower())
예제 #5
0
def test_zoom_api_request_success():
    # test successful call
    cases = [
        (None, "zoom_key", "zoom_secret"),
        ("", "zoom_key", "zoom_secret"),
    ]
    for apigee_key, zoom_key, zoom_secret in cases:
        utils.common.APIGEE_KEY = apigee_key
        utils.common.ZOOM_API_KEY = zoom_key
        utils.common.ZOOM_API_SECRET = zoom_secret

        with requests_mock.mock() as req_mock:
            req_mock.get(requests_mock.ANY,
                         status_code=200,
                         json={"mock_payload": 123})
            r = utils.zoom_api_request("meetings")
            assert "mock_payload" in r.json()
예제 #6
0
def test_zoom_api_request_failures():
    utils.common.APIGEE_KEY = None
    utils.common.ZOOM_API_KEY = "zoom_key"
    utils.common.ZOOM_API_SECRET = "zoom_secret"
    utils.common.ZOOM_API_BASE_URL = "https://api.zoom.us/v2/"
    # test failed call that returns
    with requests_mock.mock() as req_mock:
        req_mock.get(requests_mock.ANY,
                     status_code=400,
                     json={"mock_payload": 123})
        r = utils.zoom_api_request("meetings", ignore_failure=True)
        assert r.status_code == 400

    # test failed call that raises
    with requests_mock.mock() as req_mock:
        req_mock.get(requests_mock.ANY,
                     status_code=400,
                     json={"mock_payload": 123})
        error_msg = "400 Client Error"
        with pytest.raises(requests.exceptions.HTTPError, match=error_msg):
            utils.zoom_api_request("meetings", ignore_failure=False, retries=0)

    # test ConnectionError handling
    with requests_mock.mock() as req_mock:
        req_mock.get(requests_mock.ANY,
                     exc=requests.exceptions.ConnectionError)
        error_msg = "Error requesting https://api.zoom.us/v2/meetings"
        with pytest.raises(utils.common.ZoomApiRequestError, match=error_msg):
            utils.zoom_api_request("meetings")

    # test ConnectTimeout handling
    with requests_mock.mock() as req_mock:
        req_mock.get(requests_mock.ANY, exc=requests.exceptions.ConnectTimeout)
        error_msg = "Error requesting https://api.zoom.us/v2/meetings"
        with pytest.raises(utils.common.ZoomApiRequestError, match=error_msg):
            utils.zoom_api_request("meetings")
예제 #7
0
def test_zoom_api_request_missing_endpoint():
    with pytest.raises(Exception) as exc_info:
        utils.zoom_api_request(endpoint=None)
    assert exc_info.match("Call to zoom_api_request missing endpoint")
예제 #8
0
def handler(event, context):
    """
    This function acts as a relay to the traditional zoom webhook. The webhook
    function is called by Zoom on a "recording.completed" event, along with
    data about the recordings. Here we fetch the recording data from the zoom
    API.
    The response to that API call is (mostly) identical to the payload zoom
    sends to the webhook, so we can simply pass it along in our own webhook
    request, using a "on.demand.ingest" event type and including the series id
    as "on_demand_series_id".
    """

    logger.info(event)

    if "body" not in event or event["body"] is None:
        return resp(400, "Bad data. No body found in event.")

    try:
        body = json.loads(event["body"])
        logger.info({"on-demand request": body})
    except json.JSONDecodeError:
        return resp(400, "Webhook notification body is not valid json.")

    if "uuid" not in body:
        return resp(
            400,
            "Missing recording uuid field in webhook notification body.",
        )

    recording_uuid = body["uuid"]
    if recording_uuid.startswith("https"):
        # it's a link; parse the uuid from the query string
        parsed_url = urlparse(recording_uuid)
        query_params = parse_qs(parsed_url.query)
        if "meeting_id" not in query_params:
            return resp(
                404,
                "Zoom URL is malformed or missing 'meeting_id' param.",
            )
        recording_uuid = query_params["meeting_id"][0]

    logger.info(f"Got recording uuid: '{recording_uuid}'")

    try:
        try:
            # zoom api can break if uuid is not double urlencoded
            double_urlencoded_uuid = quote(
                quote(recording_uuid, safe=""),
                safe="",
            )
            zoom_endpoint = (
                f"meetings/{double_urlencoded_uuid}/recordings"
                "?include_fields=download_access_token&ttl=3600"
            )
            r = zoom_api_request(zoom_endpoint)
            recording_data = r.json()
        except requests.HTTPError as e:
            # return a 404 if there's no such meeting
            if e.response.status_code == 404:
                return resp(
                    404, f"No zoom recording with id '{recording_uuid}'"
                )
            else:
                raise
    # otherwise return a 500 on any other errors (bad json, bad request, etc)
    except Exception as e:
        return resp(
            500,
            f"Something went wrong querying the zoom api: {str(e)}",
        )

    if (
        "recording_files" not in recording_data
        or not recording_data["recording_files"]
    ):
        return resp(
            503,
            "Zoom api response contained no recording files for "
            f"{recording_uuid}",
        )

    # verify that all the recording files are actually "completed"
    not_completed = sum(
        1
        for x in recording_data["recording_files"]
        if x.get("status") and x.get("status") != "completed"
    )

    if not_completed > 0:
        return resp(
            503,
            "Not all recorded files have status 'completed'",
        )

    webhook_data = {
        "event": "on.demand.ingest",
        "payload": {
            "object": recording_data,
        },
        "download_token": recording_data["download_access_token"],
    }

    # series id is an optional param. if not present the download function will
    # attempt to determine the series id by matching the recording times
    # against it's known schedule as usual.
    if "oc_series_id" in body and body["oc_series_id"]:
        webhook_data["payload"]["on_demand_series_id"] = body["oc_series_id"]

    if "allow_multiple_ingests" in body:
        webhook_data["payload"]["allow_multiple_ingests"] = body[
            "allow_multiple_ingests"
        ]

    if "oc_workflow" in body:
        webhook_data["payload"]["oc_workflow"] = body["oc_workflow"]

    if "ingest_all_mp4" in body:
        webhook_data["payload"]["ingest_all_mp4"] = body["ingest_all_mp4"]

    zip_id = f"on-demand-{str(uuid4())}"
    webhook_data["payload"]["zip_id"] = zip_id

    logger.info({"webhook_data": webhook_data})
    try:
        r = requests.post(
            WEBHOOK_ENDPOINT_URL,
            data=json.dumps(webhook_data),
            headers={
                "Content-type": "application/json",
            },
        )
        r.raise_for_status()
        if r.status_code == 204:
            raise Exception("Webhook returned 204: ingest not accepted")
    except Exception as e:
        err_msg = str(e)
        logger.exception(
            f"Something went wrong calling the webhook: {err_msg}"
        )
        return resp(500, err_msg)

    set_pipeline_status(
        zip_id,
        PipelineStatus.ON_DEMAND_RECEIVED,
        meeting_id=webhook_data["payload"]["object"]["id"],
        recording_id=recording_uuid,
        recording_start_time=webhook_data["payload"]["object"]["start_time"],
        topic=webhook_data["payload"]["object"]["topic"],
        origin="on_demand",
        oc_series_id=body.get("oc_series_id"),
    )
    return resp(200, "Ingest accepted")
 def host_name(self):
     if not hasattr(self, "_host_name"):
         resp = zoom_api_request(f"users/{self.data['host_id']}").json()
         logger.info({"Host details": resp})
         self._host_name = f"{resp['first_name']} {resp['last_name']}"
     return self._host_name