mirror of
https://github.com/apache/superset.git
synced 2026-04-19 16:14:52 +00:00
feat(reports): add webhook option for notifications (#36127)
Co-authored-by: Hriday Algh <hridayalgh@gmail.com> Co-authored-by: Hriday Algh <53922405+AlwaysIngame@users.noreply.github.com> Co-authored-by: codeant-ai-for-open-source[bot] <244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com>
This commit is contained in:
225
tests/unit_tests/reports/notifications/webhook_tests.py
Normal file
225
tests/unit_tests/reports/notifications/webhook_tests.py
Normal file
@@ -0,0 +1,225 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from superset.reports.notifications.exceptions import (
|
||||
NotificationParamException,
|
||||
)
|
||||
from superset.reports.notifications.webhook import WebhookNotification
|
||||
from superset.utils.core import HeaderDataType
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_header_data() -> HeaderDataType:
|
||||
return {
|
||||
"notification_format": "PNG",
|
||||
"notification_type": "Alert",
|
||||
"owners": [1],
|
||||
"notification_source": None,
|
||||
"chart_id": None,
|
||||
"dashboard_id": None,
|
||||
"slack_channels": None,
|
||||
"execution_id": "test-execution-id",
|
||||
}
|
||||
|
||||
|
||||
def test_get_webhook_url(mock_header_data) -> None:
|
||||
"""
|
||||
Test the _get_webhook_url function to ensure it correctly extracts
|
||||
the webhook URL from recipient configuration
|
||||
"""
|
||||
from superset.reports.models import ReportRecipients, ReportRecipientType
|
||||
from superset.reports.notifications.base import NotificationContent
|
||||
|
||||
content = NotificationContent(
|
||||
name="test alert",
|
||||
header_data=mock_header_data,
|
||||
embedded_data=pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}),
|
||||
description="Test description",
|
||||
)
|
||||
webhook_notification = WebhookNotification(
|
||||
recipient=ReportRecipients(
|
||||
type=ReportRecipientType.WEBHOOK,
|
||||
recipient_config_json='{"target": "https://example.com/webhook"}',
|
||||
),
|
||||
content=content,
|
||||
)
|
||||
|
||||
result = webhook_notification._get_webhook_url()
|
||||
|
||||
assert result == "https://example.com/webhook"
|
||||
|
||||
|
||||
def test_get_webhook_url_missing_url(mock_header_data) -> None:
|
||||
"""
|
||||
Test that _get_webhook_url raises an exception when URL is missing
|
||||
"""
|
||||
from superset.reports.models import ReportRecipients, ReportRecipientType
|
||||
from superset.reports.notifications.base import NotificationContent
|
||||
|
||||
content = NotificationContent(
|
||||
name="test alert",
|
||||
header_data=mock_header_data,
|
||||
description="Test description",
|
||||
)
|
||||
webhook_notification = WebhookNotification(
|
||||
recipient=ReportRecipients(
|
||||
type=ReportRecipientType.WEBHOOK,
|
||||
recipient_config_json="{}",
|
||||
),
|
||||
content=content,
|
||||
)
|
||||
|
||||
with pytest.raises(NotificationParamException, match="Webhook URL is required"):
|
||||
webhook_notification._get_webhook_url()
|
||||
|
||||
|
||||
def test_get_req_payload_basic(mock_header_data) -> None:
|
||||
"""
|
||||
Test that _get_req_payload returns correct payload structure
|
||||
"""
|
||||
from superset.reports.models import ReportRecipients, ReportRecipientType
|
||||
from superset.reports.notifications.base import NotificationContent
|
||||
|
||||
content = NotificationContent(
|
||||
name="Payload Name",
|
||||
header_data=mock_header_data,
|
||||
embedded_data=None,
|
||||
description="Payload Description",
|
||||
url="http://example.com/report",
|
||||
text="Report Text",
|
||||
)
|
||||
webhook_notification = WebhookNotification(
|
||||
recipient=ReportRecipients(
|
||||
type=ReportRecipientType.WEBHOOK,
|
||||
recipient_config_json='{"target": "https://webhook.com"}',
|
||||
),
|
||||
content=content,
|
||||
)
|
||||
|
||||
payload = webhook_notification._get_req_payload()
|
||||
|
||||
assert payload["name"] == "Payload Name"
|
||||
assert payload["description"] == "Payload Description"
|
||||
assert payload["url"] == "http://example.com/report"
|
||||
assert payload["text"] == "Report Text"
|
||||
assert isinstance(payload["header"], dict)
|
||||
# Optional fields from header_data
|
||||
assert payload["header"]["notification_format"] == "PNG"
|
||||
assert payload["header"]["notification_type"] == "Alert"
|
||||
|
||||
|
||||
def test_get_files_includes_all_content_types(mock_header_data) -> None:
|
||||
"""
|
||||
Test that _get_files correctly includes csv, pdf, and multiple screenshot attachments
|
||||
""" # noqa: E501
|
||||
|
||||
from superset.reports.models import ReportRecipients, ReportRecipientType
|
||||
from superset.reports.notifications.base import NotificationContent
|
||||
|
||||
csv_bytes = b"col1,col2\n1,2"
|
||||
pdf_bytes = b"%PDF-1.4"
|
||||
screenshots = [b"fakeimg1", b"fakeimg2"]
|
||||
|
||||
content = NotificationContent(
|
||||
name="file test",
|
||||
header_data=mock_header_data,
|
||||
csv=csv_bytes,
|
||||
pdf=pdf_bytes,
|
||||
screenshots=screenshots,
|
||||
description="files test",
|
||||
)
|
||||
webhook_notification = WebhookNotification(
|
||||
recipient=ReportRecipients(
|
||||
type=ReportRecipientType.WEBHOOK,
|
||||
recipient_config_json='{"target": "https://webhook.com"}',
|
||||
),
|
||||
content=content,
|
||||
)
|
||||
files = webhook_notification._get_files()
|
||||
# There should be 1 csv, 1 pdf, and 2 screenshots = 4 files total
|
||||
assert len(files) == 4
|
||||
|
||||
file_names = [file_info[1][0] for file_info in files]
|
||||
assert "report.csv" in file_names
|
||||
assert "report.pdf" in file_names
|
||||
assert "screenshot_0.png" in file_names
|
||||
assert "screenshot_1.png" in file_names
|
||||
|
||||
mime_types = [file_info[1][2] for file_info in files]
|
||||
assert "text/csv" in mime_types
|
||||
assert "application/pdf" in mime_types
|
||||
assert mime_types.count("image/png") == 2
|
||||
|
||||
|
||||
def test_get_files_empty_when_no_content(mock_header_data) -> None:
|
||||
"""
|
||||
Test that _get_files returns empty list when no files present
|
||||
"""
|
||||
from superset.reports.models import ReportRecipients, ReportRecipientType
|
||||
from superset.reports.notifications.base import NotificationContent
|
||||
|
||||
content = NotificationContent(
|
||||
name="no files",
|
||||
header_data=mock_header_data,
|
||||
description="no files test",
|
||||
)
|
||||
webhook_notification = WebhookNotification(
|
||||
recipient=ReportRecipients(
|
||||
type=ReportRecipientType.WEBHOOK,
|
||||
recipient_config_json='{"target": "https://webhook.com"}',
|
||||
),
|
||||
content=content,
|
||||
)
|
||||
files = webhook_notification._get_files()
|
||||
assert files == []
|
||||
|
||||
|
||||
def test_send_http_only_https_check(monkeypatch, mock_header_data) -> None:
|
||||
"""
|
||||
Test send raises when URL is not HTTPS and config enforces HTTPS only
|
||||
"""
|
||||
from superset.reports.models import ReportRecipients, ReportRecipientType
|
||||
from superset.reports.notifications.base import NotificationContent
|
||||
|
||||
content = NotificationContent(
|
||||
name="test alert", header_data=mock_header_data, description="Test description"
|
||||
)
|
||||
webhook_notification = WebhookNotification(
|
||||
recipient=ReportRecipients(
|
||||
type=ReportRecipientType.WEBHOOK,
|
||||
recipient_config_json='{"target": "http://notsecure.com/webhook"}',
|
||||
),
|
||||
content=content,
|
||||
)
|
||||
|
||||
class MockCurrentApp:
|
||||
config = {"ALERT_REPORTS_WEBHOOK_HTTPS_ONLY": True}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"superset.reports.notifications.webhook.current_app", MockCurrentApp
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"superset.reports.notifications.webhook.feature_flag_manager.is_feature_enabled",
|
||||
lambda flag: True,
|
||||
)
|
||||
|
||||
with pytest.raises(NotificationParamException, match="HTTPS is required by config"):
|
||||
webhook_notification.send()
|
||||
Reference in New Issue
Block a user