-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17968 from jmchilton/error_reporting_unit_tests
Error reporting unit tests
- Loading branch information
Showing
5 changed files
with
307 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
"""Integration tests for user error reporting.""" | ||
|
||
import json | ||
import os | ||
import string | ||
|
||
from galaxy_test.base.populators import DatasetPopulator | ||
from galaxy_test.driver import integration_util | ||
|
||
JSON_ERROR_REPORTS = """ | ||
- type: json | ||
verbose: true | ||
user_submission: true | ||
directory: ${reports_directory} | ||
""" | ||
|
||
MOCK_EMAIL_ERROR_REPORTS = """ | ||
- type: email | ||
verbose: true | ||
user_submission: true | ||
""" | ||
|
||
|
||
class TestErrorReportIntegration(integration_util.IntegrationTestCase): | ||
dataset_populator: DatasetPopulator | ||
reports_directory: str | ||
framework_tool_and_types = True | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.dataset_populator = DatasetPopulator(self.galaxy_interactor) | ||
|
||
@classmethod | ||
def handle_galaxy_config_kwds(cls, config): | ||
reports_directory = cls._test_driver.mkdtemp() | ||
cls.reports_directory = reports_directory | ||
template = string.Template(JSON_ERROR_REPORTS) | ||
reports_yaml = template.safe_substitute({"reports_directory": reports_directory}) | ||
reports_conf = os.path.join(reports_directory, "error_report.yml") | ||
with open(reports_conf, "w") as f: | ||
f.write(reports_yaml) | ||
config["error_report_file"] = reports_conf | ||
|
||
def test_basic_tool_error(self): | ||
with self.dataset_populator.test_history() as history_id: | ||
response = self.dataset_populator.run_detect_errors(history_id, 6, "my stdout", "my stderr") | ||
job_id = response["jobs"][0]["id"] | ||
dataset_result = response["outputs"][0] | ||
self.dataset_populator.report_job_error(job_id, dataset_result["id"]) | ||
assert len(os.listdir(self.reports_directory)) == 2 | ||
error_json = self.read_error_report(job_id) | ||
error_dict = json.loads(error_json) | ||
assert error_dict["exit_code"] == 6 | ||
|
||
def test_tool_error_custom_message_and_email(self): | ||
with self.dataset_populator.test_history() as history_id: | ||
response = self.dataset_populator.run_detect_errors(history_id, 6, "my stdout", "my stderr") | ||
job_id = response["jobs"][0]["id"] | ||
dataset_result = response["outputs"][0] | ||
self.dataset_populator.report_job_error( | ||
job_id, dataset_result["id"], "some new details", "[email protected]" | ||
) | ||
error_json = self.read_error_report(job_id) | ||
error_dict = json.loads(error_json) | ||
assert error_dict["exit_code"] == 6 | ||
assert error_dict["message"] == "some new details" | ||
assert error_dict["email"] == "[email protected]" | ||
|
||
def read_error_report(self, job_id: str): | ||
app = self._app | ||
job_id_decoded = app.security.decode_id(job_id) | ||
with open(os.path.join(self.reports_directory, str(job_id_decoded))) as f: | ||
return f.read() | ||
|
||
|
||
class TestErrorEmailReportIntegration(integration_util.IntegrationTestCase): | ||
dataset_populator: DatasetPopulator | ||
reports_directory: str | ||
framework_tool_and_types = True | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.dataset_populator = DatasetPopulator(self.galaxy_interactor) | ||
|
||
@classmethod | ||
def handle_galaxy_config_kwds(cls, config): | ||
reports_directory = cls._test_driver.mkdtemp() | ||
cls.reports_directory = reports_directory | ||
template = string.Template(MOCK_EMAIL_ERROR_REPORTS) | ||
reports_yaml = template.safe_substitute({"reports_directory": reports_directory}) | ||
reports_conf = os.path.join(reports_directory, "error_report.yml") | ||
with open(reports_conf, "w") as f: | ||
f.write(reports_yaml) | ||
config["error_report_file"] = reports_conf | ||
config["smtp_server"] = f"mock_emails_to_path://{reports_directory}/email.json" | ||
config["error_email_to"] = "[email protected]" | ||
|
||
def test_tool_error_custom_message_and_email(self): | ||
with self.dataset_populator.test_history() as history_id: | ||
response = self.dataset_populator.run_detect_errors(history_id, 6, "my stdout", "my stderr") | ||
job_id = response["jobs"][0]["id"] | ||
dataset_result = response["outputs"][0] | ||
self.dataset_populator.report_job_error( | ||
job_id, dataset_result["id"], "some new details", "[email protected]" | ||
) | ||
error_json = self.read_most_recent_error_report() | ||
error_dict = json.loads(error_json) | ||
assert error_dict["to"] == "[email protected], [email protected]" | ||
assert error_dict["subject"] == "Galaxy tool error report from [email protected] (detect_errors)" | ||
assert "<h1>Galaxy Tool Error Report</h1>" in error_dict["html"] | ||
|
||
def read_most_recent_error_report(self): | ||
with open(os.path.join(self.reports_directory, "email.json")) as f: | ||
return f.read() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import json | ||
import shutil | ||
import tempfile | ||
from pathlib import Path | ||
|
||
from galaxy import model | ||
from galaxy.app_unittest_utils.tools_support import UsesApp | ||
from galaxy.model.base import transaction | ||
from galaxy.tools.errors import EmailErrorReporter | ||
from galaxy.util.unittest import TestCase | ||
|
||
# The email the user created their account with. | ||
TEST_USER_EMAIL = "[email protected]" | ||
# The email the user supplied when submitting the error | ||
TEST_USER_SUPPLIED_EMAIL = "[email protected]" | ||
TEST_SERVER_EMAIL_FROM = "[email protected]" | ||
TEST_SERVER_ERROR_EMAIL_TO = "[email protected]" # setup in mock config | ||
|
||
|
||
class TestErrorReporter(TestCase, UsesApp): | ||
|
||
def setUp(self): | ||
self.setup_app() | ||
self.app.config.email_from = TEST_SERVER_EMAIL_FROM | ||
self.tmp_path = Path(tempfile.mkdtemp()) | ||
self.email_path = self.tmp_path / "email.json" | ||
smtp_server = f"mock_emails_to_path://{self.email_path}" | ||
self.app.config.smtp_server = smtp_server # type: ignore[attr-defined] | ||
|
||
def tearDown(self): | ||
shutil.rmtree(self.tmp_path) | ||
|
||
def test_basic(self): | ||
user, hda = self._setup_model_objects() | ||
|
||
email_path = self.email_path | ||
assert not email_path.exists() | ||
error_report = EmailErrorReporter(hda, self.app) | ||
error_report.send_report(user, email=TEST_USER_SUPPLIED_EMAIL, message="My custom message") | ||
assert email_path.exists() | ||
text = email_path.read_text() | ||
email_json = json.loads(text) | ||
assert email_json["from"] == TEST_SERVER_EMAIL_FROM | ||
assert email_json["to"] == f"{TEST_SERVER_ERROR_EMAIL_TO}, {TEST_USER_SUPPLIED_EMAIL}" | ||
assert f"Galaxy tool error report from {TEST_USER_SUPPLIED_EMAIL}" == email_json["subject"] | ||
assert "cat1" in email_json["body"] | ||
assert "cat1" in email_json["html"] | ||
assert TEST_USER_EMAIL == email_json["reply_to"] | ||
|
||
def test_hda_security(self, tmp_path): | ||
user, hda = self._setup_model_objects() | ||
error_report = EmailErrorReporter(hda, self.app) | ||
security_agent = self.app.security_agent | ||
private_role = security_agent.create_private_user_role(user) | ||
access_action = security_agent.permitted_actions.DATASET_ACCESS.action | ||
manage_action = security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS.action | ||
permissions = {access_action: [private_role], manage_action: [private_role]} | ||
security_agent.set_all_dataset_permissions(hda.dataset, permissions) | ||
|
||
other_user = model.User(email="[email protected]", password="mockpass2") | ||
self._commit_objects([other_user]) | ||
security_agent = self.app.security_agent | ||
email_path = self.email_path | ||
assert not email_path.exists() | ||
error_report.send_report(other_user, email=TEST_USER_SUPPLIED_EMAIL, message="My custom message") | ||
# Without permissions, the email still gets sent but the supplied email is ignored | ||
# I'm not saying this is the right behavior but it is what the code does at the time of test | ||
# writing -John | ||
assert email_path.exists() | ||
text = email_path.read_text() | ||
email_json = json.loads(text) | ||
assert "[email protected]" not in email_json["to"] | ||
|
||
def test_html_sanitization(self, tmp_path): | ||
user, hda = self._setup_model_objects() | ||
email_path = self.email_path | ||
assert not email_path.exists() | ||
error_report = EmailErrorReporter(hda, self.app) | ||
error_report.send_report( | ||
user, email=TEST_USER_SUPPLIED_EMAIL, message='My custom <a href="http://sneaky.com/">message</a>' | ||
) | ||
assert email_path.exists() | ||
text = email_path.read_text() | ||
email_json = json.loads(text) | ||
html = email_json["html"] | ||
assert "<a href="http://sneaky.com/">message</a>" in html | ||
|
||
def test_redact_user_details_in_bugreport(self, tmp_path): | ||
user, hda = self._setup_model_objects() | ||
|
||
email_path = self.email_path | ||
assert not email_path.exists() | ||
error_report = EmailErrorReporter(hda, self.app) | ||
error_report.send_report( | ||
user, email=TEST_USER_SUPPLIED_EMAIL, message="My custom message", redact_user_details_in_bugreport=True | ||
) | ||
assert email_path.exists() | ||
text = email_path.read_text() | ||
email_json = json.loads(text) | ||
assert "The user redacted (user: 1) provided the following information:" in email_json["body"] | ||
assert ( | ||
"""The user <span style="font-family: monospace;">redacted (user: 1)</span> provided the following information:""" | ||
in email_json["html"] | ||
) | ||
|
||
def test_no_redact_user_details_in_bugreport(self, tmp_path): | ||
user, hda = self._setup_model_objects() | ||
|
||
email_path = self.email_path | ||
assert not email_path.exists() | ||
error_report = EmailErrorReporter(hda, self.app) | ||
error_report.send_report( | ||
user, email=TEST_USER_SUPPLIED_EMAIL, message="My custom message", redact_user_details_in_bugreport=False | ||
) | ||
assert email_path.exists() | ||
text = email_path.read_text() | ||
email_json = json.loads(text) | ||
assert ( | ||
f"The user '{TEST_USER_EMAIL}' (providing preferred contact email '{TEST_USER_SUPPLIED_EMAIL}') provided the following information:" | ||
in email_json["body"] | ||
) | ||
assert ( | ||
f"""The user <span style="font-family: monospace;">'{TEST_USER_EMAIL}' (providing preferred contact email '{TEST_USER_SUPPLIED_EMAIL}')</span> provided the following information:""" | ||
in email_json["html"] | ||
) | ||
|
||
def _setup_model_objects(self): | ||
user = model.User(email=TEST_USER_EMAIL, password="mockpass") | ||
job = model.Job() | ||
job.tool_id = "cat1" | ||
job.history = model.History() | ||
job.user = user | ||
job.history.user = user | ||
hda = model.HistoryDatasetAssociation(history=job.history) | ||
hda.dataset = model.Dataset() | ||
hda.dataset.state = "ok" | ||
job.add_output_dataset("out1", hda) | ||
self._commit_objects([job, hda, user]) | ||
return user, hda | ||
|
||
def _commit_objects(self, objects): | ||
session = self.app.model.context | ||
session.add_all(objects) | ||
with transaction(session): | ||
session.commit() |