mirror of
https://github.com/apache/superset.git
synced 2026-04-11 20:37:16 +00:00
259 lines
8.4 KiB
Python
259 lines
8.4 KiB
Python
# 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 pytest
|
|
from marshmallow import ValidationError
|
|
from pytest_mock import MockerFixture
|
|
|
|
from superset.reports.schemas import ReportSchedulePostSchema, ReportSchedulePutSchema
|
|
|
|
|
|
def test_report_post_schema_custom_width_validation(mocker: MockerFixture) -> None:
|
|
"""
|
|
Test the custom width validation.
|
|
"""
|
|
mocker.patch(
|
|
"flask.current_app.config",
|
|
{
|
|
"ALERT_REPORTS_MIN_CUSTOM_SCREENSHOT_WIDTH": 100,
|
|
"ALERT_REPORTS_MAX_CUSTOM_SCREENSHOT_WIDTH": 200,
|
|
},
|
|
)
|
|
|
|
schema = ReportSchedulePostSchema()
|
|
|
|
schema.load(
|
|
{
|
|
"type": "Report",
|
|
"name": "A report",
|
|
"description": "My report",
|
|
"active": True,
|
|
"crontab": "* * * * *",
|
|
"timezone": "America/Los_Angeles",
|
|
"custom_width": 100,
|
|
}
|
|
)
|
|
|
|
# not required
|
|
schema.load(
|
|
{
|
|
"type": "Report",
|
|
"name": "A report",
|
|
"description": "My report",
|
|
"active": True,
|
|
"crontab": "* * * * *",
|
|
"timezone": "America/Los_Angeles",
|
|
}
|
|
)
|
|
|
|
with pytest.raises(ValidationError) as excinfo:
|
|
schema.load(
|
|
{
|
|
"type": "Report",
|
|
"name": "A report",
|
|
"description": "My report",
|
|
"active": True,
|
|
"crontab": "* * * * *",
|
|
"timezone": "America/Los_Angeles",
|
|
"custom_width": 1000,
|
|
}
|
|
)
|
|
assert excinfo.value.messages == {
|
|
"custom_width": ["Screenshot width must be between 100px and 200px"]
|
|
}
|
|
|
|
|
|
MINIMAL_POST_PAYLOAD = {
|
|
"type": "Report",
|
|
"name": "A report",
|
|
"crontab": "* * * * *",
|
|
"timezone": "America/Los_Angeles",
|
|
}
|
|
|
|
CUSTOM_WIDTH_CONFIG = {
|
|
"ALERT_REPORTS_MIN_CUSTOM_SCREENSHOT_WIDTH": 600,
|
|
"ALERT_REPORTS_MAX_CUSTOM_SCREENSHOT_WIDTH": 2400,
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"schema_class,payload_base",
|
|
[
|
|
(ReportSchedulePostSchema, MINIMAL_POST_PAYLOAD),
|
|
(ReportSchedulePutSchema, {}),
|
|
],
|
|
ids=["post", "put"],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"width,should_pass",
|
|
[
|
|
(599, False),
|
|
(600, True),
|
|
(2400, True),
|
|
(2401, False),
|
|
(None, True),
|
|
],
|
|
)
|
|
def test_custom_width_boundary_values(
|
|
mocker: MockerFixture,
|
|
schema_class: type,
|
|
payload_base: dict[str, object],
|
|
width: int | None,
|
|
should_pass: bool,
|
|
) -> None:
|
|
mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG)
|
|
schema = schema_class()
|
|
payload = {**payload_base, "custom_width": width}
|
|
|
|
if should_pass:
|
|
schema.load(payload)
|
|
else:
|
|
with pytest.raises(ValidationError) as exc:
|
|
schema.load(payload)
|
|
assert "custom_width" in exc.value.messages
|
|
|
|
|
|
def test_working_timeout_validation(mocker: MockerFixture) -> None:
|
|
mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG)
|
|
post_schema = ReportSchedulePostSchema()
|
|
put_schema = ReportSchedulePutSchema()
|
|
|
|
# POST: working_timeout=0 and -1 are invalid (min=1)
|
|
with pytest.raises(ValidationError) as exc:
|
|
post_schema.load({**MINIMAL_POST_PAYLOAD, "working_timeout": 0})
|
|
assert "working_timeout" in exc.value.messages
|
|
|
|
with pytest.raises(ValidationError) as exc:
|
|
post_schema.load({**MINIMAL_POST_PAYLOAD, "working_timeout": -1})
|
|
assert "working_timeout" in exc.value.messages
|
|
|
|
# POST: working_timeout=1 is valid
|
|
post_schema.load({**MINIMAL_POST_PAYLOAD, "working_timeout": 1})
|
|
|
|
# PUT: working_timeout=None is valid (allow_none=True)
|
|
put_schema.load({"working_timeout": None})
|
|
|
|
|
|
def test_log_retention_post_vs_put_parity(mocker: MockerFixture) -> None:
|
|
mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG)
|
|
post_schema = ReportSchedulePostSchema()
|
|
put_schema = ReportSchedulePutSchema()
|
|
|
|
# POST: log_retention=0 is invalid (min=1)
|
|
with pytest.raises(ValidationError) as exc:
|
|
post_schema.load({**MINIMAL_POST_PAYLOAD, "log_retention": 0})
|
|
assert "log_retention" in exc.value.messages
|
|
|
|
# POST: log_retention=1 is valid
|
|
post_schema.load({**MINIMAL_POST_PAYLOAD, "log_retention": 1})
|
|
|
|
# PUT: log_retention=0 is valid (min=0)
|
|
put_schema.load({"log_retention": 0})
|
|
|
|
|
|
def test_report_type_disallows_database(mocker: MockerFixture) -> None:
|
|
mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG)
|
|
schema = ReportSchedulePostSchema()
|
|
|
|
with pytest.raises(ValidationError) as exc:
|
|
schema.load({**MINIMAL_POST_PAYLOAD, "database": 1})
|
|
assert "database" in exc.value.messages
|
|
|
|
|
|
def test_alert_type_allows_database(mocker: MockerFixture) -> None:
|
|
"""Alert type should accept database; only Report type blocks it."""
|
|
mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG)
|
|
schema = ReportSchedulePostSchema()
|
|
result = schema.load({**MINIMAL_POST_PAYLOAD, "type": "Alert", "database": 1})
|
|
assert result["database"] == 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Phase 1b gap closure: crontab validator, name length, PUT parity
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"crontab,should_pass",
|
|
[
|
|
("* * * * *", True),
|
|
("0 0 * * 0", True),
|
|
("*/5 * * * *", True),
|
|
("not a cron", False),
|
|
("* * * *", False), # too few fields
|
|
("", False),
|
|
],
|
|
ids=["every-min", "weekly", "every-5", "invalid-text", "too-few-fields", "empty"],
|
|
)
|
|
def test_crontab_validation(
|
|
mocker: MockerFixture,
|
|
crontab: str,
|
|
should_pass: bool,
|
|
) -> None:
|
|
mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG)
|
|
schema = ReportSchedulePostSchema()
|
|
payload = {**MINIMAL_POST_PAYLOAD, "crontab": crontab}
|
|
|
|
if should_pass:
|
|
result = schema.load(payload)
|
|
assert result["crontab"] == crontab
|
|
else:
|
|
with pytest.raises(ValidationError) as exc:
|
|
schema.load(payload)
|
|
assert "crontab" in exc.value.messages
|
|
|
|
|
|
def test_name_empty_rejected(mocker: MockerFixture) -> None:
|
|
mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG)
|
|
schema = ReportSchedulePostSchema()
|
|
|
|
with pytest.raises(ValidationError) as exc:
|
|
schema.load({**MINIMAL_POST_PAYLOAD, "name": ""})
|
|
assert "name" in exc.value.messages
|
|
|
|
|
|
def test_name_at_max_length_accepted(mocker: MockerFixture) -> None:
|
|
mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG)
|
|
schema = ReportSchedulePostSchema()
|
|
long_name = "x" * 150
|
|
result = schema.load({**MINIMAL_POST_PAYLOAD, "name": long_name})
|
|
assert result["name"] == long_name
|
|
|
|
|
|
def test_name_over_max_length_rejected(mocker: MockerFixture) -> None:
|
|
mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG)
|
|
schema = ReportSchedulePostSchema()
|
|
|
|
with pytest.raises(ValidationError) as exc:
|
|
schema.load({**MINIMAL_POST_PAYLOAD, "name": "x" * 151})
|
|
assert "name" in exc.value.messages
|
|
|
|
|
|
def test_put_schema_allows_database_on_report_type(mocker: MockerFixture) -> None:
|
|
"""PUT schema lacks validate_report_references — database on Report type is
|
|
accepted (documents current behavior; POST schema correctly rejects this)."""
|
|
mocker.patch("flask.current_app.config", CUSTOM_WIDTH_CONFIG)
|
|
put_schema = ReportSchedulePutSchema()
|
|
result = put_schema.load({"type": "Report", "database": 1})
|
|
assert result["database"] == 1
|
|
|
|
# POST schema rejects it (verify the asymmetry)
|
|
post_schema = ReportSchedulePostSchema()
|
|
with pytest.raises(ValidationError) as exc:
|
|
post_schema.load({**MINIMAL_POST_PAYLOAD, "database": 1})
|
|
assert "database" in exc.value.messages
|