mirror of
https://github.com/apache/superset.git
synced 2026-04-21 09:04:38 +00:00
feat(reports): Set a minimum interval for each report's execution (#28176)
This commit is contained in:
253
tests/unit_tests/commands/report/base_test.py
Normal file
253
tests/unit_tests/commands/report/base_test.py
Normal file
@@ -0,0 +1,253 @@
|
||||
# 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 logging
|
||||
from datetime import timedelta
|
||||
from functools import wraps
|
||||
from typing import Any, Callable
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from superset.commands.report.base import BaseReportScheduleCommand
|
||||
from superset.commands.report.exceptions import ReportScheduleFrequencyNotAllowed
|
||||
from superset.reports.models import ReportScheduleType
|
||||
|
||||
REPORT_TYPES = {
|
||||
ReportScheduleType.ALERT,
|
||||
ReportScheduleType.REPORT,
|
||||
}
|
||||
|
||||
TEST_SCHEDULES_EVERY_MINUTE = {
|
||||
"* * * * *",
|
||||
"1-5 * * * *",
|
||||
"10-20 * * * *",
|
||||
"0,45,10-20 * * * *",
|
||||
"23,45,50,51 * * * *",
|
||||
"10,20,30,40-45 * * * *",
|
||||
}
|
||||
|
||||
TEST_SCHEDULES_SINGLE_MINUTES = {
|
||||
"1,5,8,10,12 * * * *",
|
||||
"10 1 * * *",
|
||||
"27,2 1-5 * * *",
|
||||
}
|
||||
|
||||
TEST_SCHEDULES = TEST_SCHEDULES_EVERY_MINUTE.union(TEST_SCHEDULES_SINGLE_MINUTES)
|
||||
|
||||
|
||||
def app_custom_config(
|
||||
alert_minimum_interval: int | str = 0,
|
||||
report_minimum_interval: int | str = 0,
|
||||
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
||||
"""
|
||||
Decorator to mock the current_app.config values dynamically for each test.
|
||||
|
||||
:param alert_minimum_interval: Minimum interval. Defaults to None.
|
||||
:param report_minimum_interval: Minimum interval. Defaults to None.
|
||||
|
||||
:returns: A decorator that wraps a function.
|
||||
"""
|
||||
|
||||
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||
@wraps(func)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
with patch(
|
||||
"superset.commands.report.base.current_app.config"
|
||||
) as mock_config:
|
||||
mock_config.get.side_effect = lambda key, default=0: {
|
||||
"ALERT_MINIMUM_INTERVAL": alert_minimum_interval,
|
||||
"REPORT_MINIMUM_INTERVAL": report_minimum_interval,
|
||||
}.get(key, default)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@pytest.mark.parametrize("report_type", REPORT_TYPES)
|
||||
@pytest.mark.parametrize("schedule", TEST_SCHEDULES)
|
||||
@app_custom_config()
|
||||
def test_validate_report_frequency(report_type: str, schedule: str) -> None:
|
||||
"""
|
||||
Test the ``validate_report_frequency`` method when there's
|
||||
no minimum frequency configured.
|
||||
"""
|
||||
BaseReportScheduleCommand().validate_report_frequency(
|
||||
schedule,
|
||||
report_type,
|
||||
)
|
||||
|
||||
|
||||
@app_custom_config(
|
||||
alert_minimum_interval=int(timedelta(minutes=4).total_seconds()),
|
||||
report_minimum_interval=int(timedelta(minutes=5).total_seconds()),
|
||||
)
|
||||
def test_validate_report_frequency_minimum_set() -> None:
|
||||
"""
|
||||
Test the ``validate_report_frequency`` method when there's
|
||||
minimum frequencies configured.
|
||||
"""
|
||||
|
||||
BaseReportScheduleCommand().validate_report_frequency(
|
||||
"1,5 * * * *",
|
||||
ReportScheduleType.ALERT,
|
||||
)
|
||||
BaseReportScheduleCommand().validate_report_frequency(
|
||||
"6,11 * * * *",
|
||||
ReportScheduleType.REPORT,
|
||||
)
|
||||
|
||||
|
||||
@app_custom_config(
|
||||
alert_minimum_interval=int(timedelta(minutes=2).total_seconds()),
|
||||
report_minimum_interval=int(timedelta(minutes=5).total_seconds()),
|
||||
)
|
||||
def test_validate_report_frequency_invalid_schedule() -> None:
|
||||
"""
|
||||
Test the ``validate_report_frequency`` method when the configured
|
||||
schedule exceeds the limit.
|
||||
"""
|
||||
with pytest.raises(ReportScheduleFrequencyNotAllowed):
|
||||
BaseReportScheduleCommand().validate_report_frequency(
|
||||
"1,2 * * * *",
|
||||
ReportScheduleType.ALERT,
|
||||
)
|
||||
|
||||
with pytest.raises(ReportScheduleFrequencyNotAllowed):
|
||||
BaseReportScheduleCommand().validate_report_frequency(
|
||||
"1,5 * * * *",
|
||||
ReportScheduleType.REPORT,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("schedule", TEST_SCHEDULES)
|
||||
@app_custom_config(
|
||||
alert_minimum_interval=int(timedelta(minutes=10).total_seconds()),
|
||||
)
|
||||
def test_validate_report_frequency_alert_only(schedule: str) -> None:
|
||||
"""
|
||||
Test the ``validate_report_frequency`` method when there's
|
||||
only a configuration for alerts and user is creating report.
|
||||
"""
|
||||
BaseReportScheduleCommand().validate_report_frequency(
|
||||
schedule,
|
||||
ReportScheduleType.REPORT,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("schedule", TEST_SCHEDULES)
|
||||
@app_custom_config(
|
||||
report_minimum_interval=int(timedelta(minutes=10).total_seconds()),
|
||||
)
|
||||
def test_validate_report_frequency_report_only(schedule: str) -> None:
|
||||
"""
|
||||
Test the ``validate_report_frequency`` method when there's
|
||||
only a configuration for reports and user is creating alert.
|
||||
"""
|
||||
BaseReportScheduleCommand().validate_report_frequency(
|
||||
schedule,
|
||||
ReportScheduleType.ALERT,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("report_type", REPORT_TYPES)
|
||||
@pytest.mark.parametrize("schedule", TEST_SCHEDULES)
|
||||
@app_custom_config(
|
||||
alert_minimum_interval=int(timedelta(minutes=1).total_seconds()),
|
||||
report_minimum_interval=int(timedelta(minutes=1).total_seconds()),
|
||||
)
|
||||
def test_validate_report_frequency_accepts_every_minute_with_one(
|
||||
report_type: str, schedule: str
|
||||
) -> None:
|
||||
"""
|
||||
Test the ``validate_report_frequency`` method when configuration
|
||||
is set to `1`. Validates the usage of `-` and `*` in the cron.
|
||||
"""
|
||||
BaseReportScheduleCommand().validate_report_frequency(
|
||||
schedule,
|
||||
report_type,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("report_type", REPORT_TYPES)
|
||||
@pytest.mark.parametrize("schedule", TEST_SCHEDULES_SINGLE_MINUTES)
|
||||
@app_custom_config(
|
||||
alert_minimum_interval=int(timedelta(minutes=2).total_seconds()),
|
||||
report_minimum_interval=int(timedelta(minutes=2).total_seconds()),
|
||||
)
|
||||
def test_validate_report_frequency_accepts_every_minute_with_two(
|
||||
report_type: str,
|
||||
schedule: str,
|
||||
) -> None:
|
||||
"""
|
||||
Test the ``validate_report_frequency`` method when configuration
|
||||
is set to `2`.
|
||||
"""
|
||||
BaseReportScheduleCommand().validate_report_frequency(
|
||||
schedule,
|
||||
report_type,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("report_type", REPORT_TYPES)
|
||||
@pytest.mark.parametrize("schedule", TEST_SCHEDULES_EVERY_MINUTE)
|
||||
@app_custom_config(
|
||||
alert_minimum_interval=int(timedelta(minutes=2).total_seconds()),
|
||||
report_minimum_interval=int(timedelta(minutes=2).total_seconds()),
|
||||
)
|
||||
def test_validate_report_frequency_accepts_every_minute_with_two_raises(
|
||||
report_type: str,
|
||||
schedule: str,
|
||||
) -> None:
|
||||
"""
|
||||
Test the ``validate_report_frequency`` method when configuration
|
||||
is set to `2`. Validates the usage of `-` and `*` in the cron.
|
||||
"""
|
||||
# Should fail for schedules with `-` and `*`
|
||||
with pytest.raises(ReportScheduleFrequencyNotAllowed):
|
||||
BaseReportScheduleCommand().validate_report_frequency(
|
||||
schedule,
|
||||
report_type,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("report_type", REPORT_TYPES)
|
||||
@pytest.mark.parametrize("schedule", TEST_SCHEDULES)
|
||||
@app_custom_config(
|
||||
alert_minimum_interval="10 minutes",
|
||||
report_minimum_interval="10 minutes",
|
||||
)
|
||||
def test_validate_report_frequency_invalid_config(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
report_type: str,
|
||||
schedule: str,
|
||||
) -> None:
|
||||
"""
|
||||
Test the ``validate_report_frequency`` method when the configuration
|
||||
is invalid.
|
||||
"""
|
||||
caplog.set_level(logging.ERROR)
|
||||
BaseReportScheduleCommand().validate_report_frequency(
|
||||
schedule,
|
||||
report_type,
|
||||
)
|
||||
expected_error_message = (
|
||||
f"invalid value for {report_type}_MINIMUM_INTERVAL: 10 minutes"
|
||||
)
|
||||
assert expected_error_message.lower() in caplog.text.lower()
|
||||
Reference in New Issue
Block a user