def test_email_chart_no_alert(create_no_alert_email_chart): """ ExecuteReport Command: Test chart email no alert """ with freeze_time("2020-01-01T00:00:00Z"): AsyncExecuteReportScheduleCommand(create_no_alert_email_chart.id, datetime.utcnow()).run() assert_log(ReportState.NOOP)
def test_report_schedule_not_found(create_report_slack_chart): """ ExecuteReport Command: Test report schedule not found """ max_id = db.session.query(func.max(ReportSchedule.id)).scalar() with pytest.raises(ReportScheduleNotFoundError): AsyncExecuteReportScheduleCommand(test_id, max_id + 1, datetime.utcnow()).run()
def test_invalid_sql_alert(create_invalid_sql_alert_email_chart): """ ExecuteReport Command: Test alert with invalid SQL statements """ with freeze_time("2020-01-01T00:00:00Z"): with pytest.raises((AlertQueryError, AlertQueryInvalidTypeError)): AsyncExecuteReportScheduleCommand( create_invalid_sql_alert_email_chart.id, datetime.utcnow() ).run()
def test_email_mul_alert(create_mul_alert_email_chart): """ ExecuteReport Command: Test chart email multiple rows """ with freeze_time("2020-01-01T00:00:00Z"): with pytest.raises( (AlertQueryMultipleRowsError, AlertQueryMultipleColumnsError)): AsyncExecuteReportScheduleCommand(create_mul_alert_email_chart.id, datetime.utcnow()).run()
def execute(report_schedule_id: int, scheduled_dttm: str) -> None: try: scheduled_dttm_ = parser.parse(scheduled_dttm) AsyncExecuteReportScheduleCommand(report_schedule_id, scheduled_dttm_).run() except ReportScheduleUnexpectedError as ex: logger.error("An unexpected occurred while executing the report: %s", ex) except CommandException as ex: logger.info("Report state: %s", ex)
def test_grace_period_error(email_mock, create_invalid_sql_alert_email_chart): """ ExecuteReport Command: Test alert grace period on error """ with freeze_time("2020-01-01T00:00:00Z"): with pytest.raises((AlertQueryError, AlertQueryInvalidTypeError)): AsyncExecuteReportScheduleCommand( TEST_ID, create_invalid_sql_alert_email_chart.id, datetime.utcnow() ).run() # Only needed for MySQL, understand why db.session.commit() notification_targets = get_target_from_report_schedule( create_invalid_sql_alert_email_chart ) # Assert the email smtp address, asserts a notification was sent with the error assert email_mock.call_args[0][0] == OWNER_EMAIL assert ( get_notification_error_sent_count(create_invalid_sql_alert_email_chart) == 1 ) with freeze_time("2020-01-01T00:30:00Z"): with pytest.raises((AlertQueryError, AlertQueryInvalidTypeError)): AsyncExecuteReportScheduleCommand( TEST_ID, create_invalid_sql_alert_email_chart.id, datetime.utcnow() ).run() db.session.commit() assert ( get_notification_error_sent_count(create_invalid_sql_alert_email_chart) == 1 ) # Grace period ends, assert a notification was sent with freeze_time("2020-01-01T01:30:00Z"): with pytest.raises((AlertQueryError, AlertQueryInvalidTypeError)): AsyncExecuteReportScheduleCommand( TEST_ID, create_invalid_sql_alert_email_chart.id, datetime.utcnow() ).run() db.session.commit() assert ( get_notification_error_sent_count(create_invalid_sql_alert_email_chart) == 2 )
def test_report_schedule_working(create_report_slack_chart_working): """ ExecuteReport Command: Test report schedule still working """ # setup screenshot mock with pytest.raises(ReportSchedulePreviousWorkingError): AsyncExecuteReportScheduleCommand(create_report_slack_chart_working.id, datetime.utcnow()).run() assert_log(ReportLogState.ERROR, error_message=ReportSchedulePreviousWorkingError.message) assert create_report_slack_chart_working.last_state == ReportLogState.WORKING
def test_invalid_sql_alert(email_mock, create_invalid_sql_alert_email_chart): """ ExecuteReport Command: Test alert with invalid SQL statements """ with freeze_time("2020-01-01T00:00:00Z"): with pytest.raises((AlertQueryError, AlertQueryInvalidTypeError)): AsyncExecuteReportScheduleCommand( TEST_ID, create_invalid_sql_alert_email_chart.id, datetime.utcnow()).run() notification_targets = get_target_from_report_schedule( create_invalid_sql_alert_email_chart) # Assert the email smtp address, asserts a notification was sent with the error assert email_mock.call_args[0][0] == OWNER_EMAIL
def test_report_schedule_success_grace_end(create_alert_slack_chart_grace): """ ExecuteReport Command: Test report schedule on grace to noop """ # set current time to within the grace period current_time = create_alert_slack_chart_grace.last_eval_dttm + timedelta( seconds=create_alert_slack_chart_grace.grace_period + 1) with freeze_time(current_time): AsyncExecuteReportScheduleCommand(create_alert_slack_chart_grace.id, datetime.utcnow()).run() db.session.commit() assert create_alert_slack_chart_grace.last_state == ReportState.NOOP
def test_report_schedule_success_grace(create_alert_slack_chart_success): """ ExecuteReport Command: Test report schedule on success to grace """ # set current time to within the grace period current_time = create_alert_slack_chart_success.last_eval_dttm + timedelta( seconds=create_alert_slack_chart_success.grace_period - 10) with freeze_time(current_time): AsyncExecuteReportScheduleCommand(TEST_ID, create_alert_slack_chart_success.id, datetime.utcnow()).run() db.session.commit() assert create_alert_slack_chart_success.last_state == ReportState.GRACE
def test_email_disable_screenshot(email_mock, create_alert_email_chart): """ ExecuteReport Command: Test soft timeout on screenshot """ AsyncExecuteReportScheduleCommand( TEST_ID, create_alert_email_chart.id, datetime.utcnow() ).run() notification_targets = get_target_from_report_schedule(create_alert_email_chart) # Assert the email smtp address, asserts a notification was sent with the error assert email_mock.call_args[0][0] == notification_targets[0] # Assert the there is no attached image assert email_mock.call_args[1]["images"] is None assert_log(ReportState.SUCCESS)
def test_email_chart_report_dry_run( screenshot_mock, email_mock, create_report_email_chart, ): """ ExecuteReport Command: Test chart email report schedule dry run """ # setup screenshot mock screenshot_mock.return_value = SCREENSHOT_FILE app.config["ALERT_REPORTS_NOTIFICATION_DRY_RUN"] = True with freeze_time("2020-01-01T00:00:00Z"): AsyncExecuteReportScheduleCommand( TEST_ID, create_report_email_chart.id, datetime.utcnow() ).run() email_mock.assert_not_called() app.config["ALERT_REPORTS_NOTIFICATION_DRY_RUN"] = False
def test_report_schedule_working(create_report_slack_chart_working): """ ExecuteReport Command: Test report schedule still working """ # setup screenshot mock with freeze_time("2020-01-01T00:00:00Z"): with pytest.raises(ReportSchedulePreviousWorkingError): AsyncExecuteReportScheduleCommand( TEST_ID, create_report_slack_chart_working.id, datetime.utcnow() ).run() assert_log( ReportState.WORKING, error_message=ReportSchedulePreviousWorkingError.message, ) assert create_report_slack_chart_working.last_state == ReportState.WORKING
def test_soft_timeout_screenshot(screenshot_mock, email_mock, create_alert_email_chart): """ ExecuteReport Command: Test soft timeout on screenshot """ from celery.exceptions import SoftTimeLimitExceeded screenshot_mock.side_effect = SoftTimeLimitExceeded() with pytest.raises(ReportScheduleScreenshotTimeout): AsyncExecuteReportScheduleCommand(TEST_ID, create_alert_email_chart.id, datetime.utcnow()).run() # Assert the email smtp address, asserts a notification was sent with the error assert email_mock.call_args[0][0] == OWNER_EMAIL assert_log(ReportState.ERROR, error_message="A timeout occurred while taking a screenshot.")
def execute(report_schedule_id: int, scheduled_dttm: str) -> None: task_id = None try: task_id = execute.request.id scheduled_dttm_ = parser.parse(scheduled_dttm) AsyncExecuteReportScheduleCommand( task_id, report_schedule_id, scheduled_dttm_, ).run() except ReportScheduleUnexpectedError: logger.exception( "An unexpected occurred while executing the report: %s", task_id) except CommandException: logger.exception( "A downstream exception occurred while generating" " a report: %s", task_id)
def test_alert_limit_is_applied(screenshot_mock, email_mock, create_alert_email_chart): """ ExecuteReport Command: Test that all alerts apply a SQL limit to stmts """ with patch.object( create_alert_email_chart.database.db_engine_spec, "execute", return_value=None ) as execute_mock: with patch.object( create_alert_email_chart.database.db_engine_spec, "fetch_data", return_value=None, ) as fetch_data_mock: AsyncExecuteReportScheduleCommand( test_id, create_alert_email_chart.id, datetime.utcnow() ).run() assert "LIMIT 2" in execute_mock.call_args[0][1]
def test_slack_token_callable_chart_report( screenshot_mock, slack_client_mock_class, create_report_slack_chart, ): """ ExecuteReport Command: Test chart slack alert (slack token callable) """ slack_client_mock_class.return_value = Mock() app.config["SLACK_API_TOKEN"] = Mock(return_value="cool_code") # setup screenshot mock screenshot_mock.return_value = SCREENSHOT_FILE with freeze_time("2020-01-01T00:00:00Z"): AsyncExecuteReportScheduleCommand( TEST_ID, create_report_slack_chart.id, datetime.utcnow() ).run() app.config["SLACK_API_TOKEN"].assert_called_once() assert slack_client_mock_class.called_with(token="cool_code", proxy="") assert_log(ReportState.SUCCESS)
def test_slack_chart_alert_no_attachment(email_mock, create_alert_email_chart): """ ExecuteReport Command: Test chart slack alert """ # setup screenshot mock with freeze_time("2020-01-01T00:00:00Z"): AsyncExecuteReportScheduleCommand( TEST_ID, create_alert_email_chart.id, datetime.utcnow() ).run() notification_targets = get_target_from_report_schedule(create_alert_email_chart) # Assert the email smtp address assert email_mock.call_args[0][0] == notification_targets[0] # Assert the there is no attached image assert email_mock.call_args[1]["images"] is None # Assert logs are correct assert_log(ReportState.SUCCESS)
def test_email_dashboard_report_fails( screenshot_mock, email_mock, create_report_email_dashboard ): """ ExecuteReport Command: Test dashboard email report schedule notification fails """ # setup screenshot mock from smtplib import SMTPException screenshot_mock.return_value = SCREENSHOT_FILE email_mock.side_effect = SMTPException("Could not connect to SMTP XPTO") with pytest.raises(ReportScheduleNotificationError): AsyncExecuteReportScheduleCommand( TEST_ID, create_report_email_dashboard.id, datetime.utcnow() ).run() assert_log(ReportState.ERROR, error_message="Could not connect to SMTP XPTO")
def test_fail_screenshot(screenshot_mock, email_mock, create_report_email_chart): """ ExecuteReport Command: Test soft timeout on screenshot """ from celery.exceptions import SoftTimeLimitExceeded from superset.reports.commands.exceptions import AlertQueryTimeout screenshot_mock.side_effect = Exception("Unexpected error") with pytest.raises(ReportScheduleScreenshotFailedError): AsyncExecuteReportScheduleCommand( test_id, create_report_email_chart.id, datetime.utcnow() ).run() notification_targets = get_target_from_report_schedule(create_report_email_chart) # Assert the email smtp address, asserts a notification was sent with the error assert email_mock.call_args[0][0] == notification_targets[0] assert_log( ReportState.ERROR, error_message="Failed taking a screenshot Unexpected error" )
def test_slack_chart_report_schedule(screenshot_mock, file_upload_mock, create_report_slack_chart): """ ExecuteReport Command: Test chart slack report schedule """ # setup screenshot mock screenshot = read_fixture("sample.png") screenshot_mock.return_value = screenshot with freeze_time("2020-01-01T00:00:00Z"): AsyncExecuteReportScheduleCommand(create_report_slack_chart.id, datetime.utcnow()).run() notification_targets = get_target_from_report_schedule( create_report_slack_chart) assert file_upload_mock.call_args[1][ "channels"] == notification_targets[0] assert file_upload_mock.call_args[1]["file"] == screenshot # Assert logs are correct assert_log(ReportState.SUCCESS)
def test_report_schedule_working_timeout(create_report_slack_chart_working): """ ExecuteReport Command: Test report schedule still working but should timed out """ # setup screenshot mock current_time = create_report_slack_chart_working.last_eval_dttm + timedelta( seconds=create_report_slack_chart_working.working_timeout + 1) with freeze_time(current_time): with pytest.raises(ReportScheduleWorkingTimeoutError): AsyncExecuteReportScheduleCommand( create_report_slack_chart_working.id, datetime.utcnow()).run() # Only needed for MySQL, understand why db.session.commit() logs = db.session.query(ReportExecutionLog).all() assert len(logs) == 1 assert logs[0].error_message == ReportScheduleWorkingTimeoutError.message assert logs[0].state == ReportState.ERROR assert create_report_slack_chart_working.last_state == ReportState.ERROR
def test_slack_chart_alert(screenshot_mock, email_mock, create_alert_email_chart): """ ExecuteReport Command: Test chart slack alert """ # setup screenshot mock screenshot = read_fixture("sample.png") screenshot_mock.return_value = screenshot with freeze_time("2020-01-01T00:00:00Z"): AsyncExecuteReportScheduleCommand( create_alert_email_chart.id, datetime.utcnow() ).run() notification_targets = get_target_from_report_schedule(create_alert_email_chart) # Assert the email smtp address assert email_mock.call_args[0][0] == notification_targets[0] # Assert the email inline screenshot smtp_images = email_mock.call_args[1]["images"] assert smtp_images[list(smtp_images.keys())[0]] == screenshot # Assert logs are correct assert_log(ReportState.SUCCESS)
def test_email_dashboard_report_schedule(screenshot_mock, email_mock, create_report_email_dashboard): """ ExecuteReport Command: Test dashboard email report schedule """ # setup screenshot mock screenshot_mock.return_value = SCREENSHOT_FILE with freeze_time("2020-01-01T00:00:00Z"): AsyncExecuteReportScheduleCommand(TEST_ID, create_report_email_dashboard.id, datetime.utcnow()).run() notification_targets = get_target_from_report_schedule( create_report_email_dashboard) # Assert the email smtp address assert email_mock.call_args[0][0] == notification_targets[0] # Assert the email inline screenshot smtp_images = email_mock.call_args[1]["images"] assert smtp_images[list(smtp_images.keys())[0]] == SCREENSHOT_FILE # Assert logs are correct assert_log(ReportState.SUCCESS)
def test_soft_timeout_alert(email_mock, create_alert_email_chart): """ ExecuteReport Command: Test soft timeout on alert queries """ from celery.exceptions import SoftTimeLimitExceeded from superset.reports.commands.exceptions import AlertQueryTimeout with patch.object(create_alert_email_chart.database.db_engine_spec, "execute", return_value=None) as execute_mock: execute_mock.side_effect = SoftTimeLimitExceeded() with pytest.raises(AlertQueryTimeout): AsyncExecuteReportScheduleCommand(create_alert_email_chart.id, datetime.utcnow()).run() notification_targets = get_target_from_report_schedule( create_alert_email_chart) # Assert the email smtp address, asserts a notification was sent with the error assert email_mock.call_args[0][0] == notification_targets[0] assert_log(ReportState.ERROR, error_message="A timeout occurred while executing the query.")
def test_fail_csv(csv_mock, mock_open, mock_urlopen, email_mock, create_report_email_chart_with_csv): """ ExecuteReport Command: Test error on csv """ response = Mock() mock_open.return_value = response mock_urlopen.return_value = response mock_urlopen.return_value.getcode.return_value = 500 with pytest.raises(ReportScheduleCsvFailedError): AsyncExecuteReportScheduleCommand( TEST_ID, create_report_email_chart_with_csv.id, datetime.utcnow()).run() notification_targets = get_target_from_report_schedule( create_report_email_chart_with_csv) # Assert the email smtp address, asserts a notification was sent with the error assert email_mock.call_args[0][0] == OWNER_EMAIL assert_log(ReportState.ERROR, error_message="Failed generating csv <urlopen error 500>")
def test_email_chart_report_schedule_with_csv( csv_mock, email_mock, mock_open, mock_urlopen, create_report_email_chart_with_csv, ): """ ExecuteReport Command: Test chart email report schedule with CSV """ # setup csv mock response = Mock() mock_open.return_value = response mock_urlopen.return_value = response mock_urlopen.return_value.getcode.return_value = 200 response.read.return_value = CSV_FILE with freeze_time("2020-01-01T00:00:00Z"): AsyncExecuteReportScheduleCommand( TEST_ID, create_report_email_chart_with_csv.id, datetime.utcnow()).run() notification_targets = get_target_from_report_schedule( create_report_email_chart_with_csv) # assert that the link sent is correct assert ( f'<a href="http://0.0.0.0:8080/superset/slice/' f'{create_report_email_chart_with_csv.chart.id}/">Explore in Superset</a>' in email_mock.call_args[0][2]) # Assert the email smtp address assert email_mock.call_args[0][0] == notification_targets[0] # Assert the email csv file smtp_images = email_mock.call_args[1]["data"] assert smtp_images[list(smtp_images.keys())[0]] == CSV_FILE # Assert logs are correct assert_log(ReportState.SUCCESS)
def test_grace_period_error_flap( screenshot_mock, email_mock, create_invalid_sql_alert_email_chart, ): """ ExecuteReport Command: Test alert grace period on error """ with freeze_time("2020-01-01T00:00:00Z"): with pytest.raises((AlertQueryError, AlertQueryInvalidTypeError)): AsyncExecuteReportScheduleCommand( TEST_ID, create_invalid_sql_alert_email_chart.id, datetime.utcnow() ).run() db.session.commit() # Assert we have 1 notification sent on the log assert ( get_notification_error_sent_count(create_invalid_sql_alert_email_chart) == 1 ) with freeze_time("2020-01-01T00:30:00Z"): with pytest.raises((AlertQueryError, AlertQueryInvalidTypeError)): AsyncExecuteReportScheduleCommand( TEST_ID, create_invalid_sql_alert_email_chart.id, datetime.utcnow() ).run() db.session.commit() assert ( get_notification_error_sent_count(create_invalid_sql_alert_email_chart) == 1 ) # Change report_schedule to valid create_invalid_sql_alert_email_chart.sql = "SELECT 1 AS metric" create_invalid_sql_alert_email_chart.grace_period = 0 db.session.merge(create_invalid_sql_alert_email_chart) db.session.commit() with freeze_time("2020-01-01T00:31:00Z"): # One success AsyncExecuteReportScheduleCommand( TEST_ID, create_invalid_sql_alert_email_chart.id, datetime.utcnow() ).run() # Grace period ends AsyncExecuteReportScheduleCommand( TEST_ID, create_invalid_sql_alert_email_chart.id, datetime.utcnow() ).run() db.session.commit() create_invalid_sql_alert_email_chart.sql = "SELECT 'first'" create_invalid_sql_alert_email_chart.grace_period = 10 db.session.merge(create_invalid_sql_alert_email_chart) db.session.commit() # assert that after a success, when back to error we send the error notification # again with freeze_time("2020-01-01T00:32:00Z"): with pytest.raises((AlertQueryError, AlertQueryInvalidTypeError)): AsyncExecuteReportScheduleCommand( TEST_ID, create_invalid_sql_alert_email_chart.id, datetime.utcnow() ).run() db.session.commit() assert ( get_notification_error_sent_count(create_invalid_sql_alert_email_chart) == 2 )