|
1 | 1 | from datetime import datetime, timezone |
2 | 2 | from typing import Callable, NamedTuple |
| 3 | +from unittest.mock import Mock |
3 | 4 |
|
4 | 5 | import pytest |
| 6 | +from requests.exceptions import InvalidURL |
5 | 7 |
|
6 | 8 | from datadog_checks.base.stubs.aggregator import AggregatorStub |
7 | 9 | from datadog_checks.dev.utils import get_metadata_metrics |
8 | 10 | from datadog_checks.prefect import PrefectCheck |
| 11 | +from datadog_checks.prefect.check import PrefectClient |
9 | 12 |
|
10 | 13 | WP1_TAGS = ["work_pool_id:wp-1", "work_pool_name:default-pool", "work_pool_type:process"] |
11 | 14 | WP2_TAGS = ["work_pool_id:wp-2", "work_pool_name:paused-pool", "work_pool_type:docker"] |
@@ -600,6 +603,62 @@ class EventCase(NamedTuple): |
600 | 603 | pytestmark = [pytest.mark.usefixtures("mock_prefect_client"), pytest.mark.unit] |
601 | 604 |
|
602 | 605 |
|
| 606 | +class MockResponse: |
| 607 | + def __init__(self, payload: dict): |
| 608 | + self.payload = payload |
| 609 | + |
| 610 | + def raise_for_status(self): |
| 611 | + return None |
| 612 | + |
| 613 | + def json(self) -> dict: |
| 614 | + return self.payload |
| 615 | + |
| 616 | + |
| 617 | +def test_paginate_events_rejects_external_next_page(): |
| 618 | + http = Mock() |
| 619 | + http.post.return_value = MockResponse( |
| 620 | + { |
| 621 | + "events": [{"id": "event-1"}], |
| 622 | + "next_page": "http://attacker.example/evil", |
| 623 | + } |
| 624 | + ) |
| 625 | + log = Mock() |
| 626 | + client = PrefectClient("http://prefect.local/api", http, log) |
| 627 | + |
| 628 | + events = client.paginate_events("/events/filter", {}) |
| 629 | + |
| 630 | + assert events == [{"id": "event-1"}] |
| 631 | + http.get.assert_not_called() |
| 632 | + log.error.assert_called_once() |
| 633 | + args = log.error.call_args[0] |
| 634 | + assert args[0] == "Could not collect next page of events: %s, data is incomplete" |
| 635 | + assert isinstance(args[1], InvalidURL) |
| 636 | + assert str(args[1]) == "Invalid next_page URL with unexpected host: http://attacker.example/evil" |
| 637 | + |
| 638 | + |
| 639 | +def test_paginate_events_allows_same_host_absolute_next_page(): |
| 640 | + http = Mock() |
| 641 | + http.post.return_value = MockResponse( |
| 642 | + { |
| 643 | + "events": [{"id": "event-1"}], |
| 644 | + "next_page": "http://prefect.local/api/events/filter?page=2", |
| 645 | + } |
| 646 | + ) |
| 647 | + http.get.return_value = MockResponse( |
| 648 | + { |
| 649 | + "events": [{"id": "event-2"}], |
| 650 | + "next_page": None, |
| 651 | + } |
| 652 | + ) |
| 653 | + log = Mock() |
| 654 | + client = PrefectClient("http://prefect.local/api", http, log) |
| 655 | + |
| 656 | + events = client.paginate_events("/events/filter", {}) |
| 657 | + |
| 658 | + assert events == [{"id": "event-1"}, {"id": "event-2"}] |
| 659 | + http.get.assert_called_once_with("http://prefect.local/api/events/filter?page=2") |
| 660 | + |
| 661 | + |
603 | 662 | @pytest.fixture() |
604 | 663 | def ready_check(check: PrefectCheck, dd_run_check: Callable, aggregator, mocker) -> PrefectCheck: |
605 | 664 | mocker.patch( |
|
0 commit comments