mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
222 lines
7.2 KiB
Python
222 lines
7.2 KiB
Python
import logging
|
|
from unittest.mock import Mock, patch
|
|
|
|
import pytest
|
|
|
|
from src.config.sentry_config import SentryConfig
|
|
from src.errors.task_runtime_error import TaskRuntimeError
|
|
from src.sentry import TaskRunnerSentry, setup_sentry
|
|
from src.constants import (
|
|
EXECUTOR_ALL_ITEMS_FILENAME,
|
|
EXECUTOR_PER_ITEM_FILENAME,
|
|
LOG_SENTRY_MISSING,
|
|
SENTRY_TAG_SERVER_TYPE_KEY,
|
|
SENTRY_TAG_SERVER_TYPE_VALUE,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def sentry_config():
|
|
return SentryConfig(
|
|
dsn="https://test@sentry.io/123456",
|
|
n8n_version="1.0.0",
|
|
environment="test",
|
|
deployment_name="test-deployment",
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def disabled_sentry_config():
|
|
return SentryConfig(
|
|
dsn="",
|
|
n8n_version="1.0.0",
|
|
environment="test",
|
|
deployment_name="test-deployment",
|
|
)
|
|
|
|
|
|
class TestTaskRunnerSentry:
|
|
def test_init_configures_sentry_correctly(self, sentry_config):
|
|
with (
|
|
patch("sentry_sdk.init") as mock_init,
|
|
patch("sentry_sdk.set_tag") as mock_set_tag,
|
|
patch("sentry_sdk.integrations.logging.LoggingIntegration") as mock_logging,
|
|
):
|
|
mock_logging_instance = Mock()
|
|
mock_logging.return_value = mock_logging_instance
|
|
sentry = TaskRunnerSentry(sentry_config)
|
|
|
|
sentry.init()
|
|
|
|
mock_init.assert_called_once_with(
|
|
dsn="https://test@sentry.io/123456",
|
|
release="n8n@1.0.0",
|
|
environment="test",
|
|
server_name="test-deployment",
|
|
before_send=sentry._filter_out_ignored_errors,
|
|
attach_stacktrace=True,
|
|
send_default_pii=False,
|
|
auto_enabling_integrations=False,
|
|
default_integrations=True,
|
|
integrations=[mock_logging_instance],
|
|
)
|
|
mock_set_tag.assert_called_once_with(
|
|
SENTRY_TAG_SERVER_TYPE_KEY, SENTRY_TAG_SERVER_TYPE_VALUE
|
|
)
|
|
|
|
def test_shutdown_flushes_sentry(self, sentry_config):
|
|
with patch("sentry_sdk.flush") as mock_flush:
|
|
sentry = TaskRunnerSentry(sentry_config)
|
|
|
|
sentry.shutdown()
|
|
|
|
mock_flush.assert_called_once_with(timeout=2.0)
|
|
|
|
def test_filter_out_task_runtime_errors(self, sentry_config):
|
|
sentry = TaskRunnerSentry(sentry_config)
|
|
event = {"exception": {"values": []}}
|
|
hint = {"exc_info": (TaskRuntimeError, None, None)}
|
|
|
|
result = sentry._filter_out_ignored_errors(event, hint)
|
|
|
|
assert result is None
|
|
|
|
def test_filter_out_user_code_errors_from_executors(self, sentry_config):
|
|
sentry = TaskRunnerSentry(sentry_config)
|
|
|
|
for executor_filename in [
|
|
EXECUTOR_ALL_ITEMS_FILENAME,
|
|
EXECUTOR_PER_ITEM_FILENAME,
|
|
]:
|
|
event = {
|
|
"exception": {
|
|
"values": [
|
|
{
|
|
"stacktrace": {
|
|
"frames": [
|
|
{"filename": "some_file.py"},
|
|
{"filename": executor_filename},
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
hint = {}
|
|
|
|
result = sentry._filter_out_ignored_errors(event, hint)
|
|
|
|
assert result is None
|
|
|
|
def test_allows_non_user_code_errors(self, sentry_config):
|
|
sentry = TaskRunnerSentry(sentry_config)
|
|
event = {
|
|
"exception": {
|
|
"values": [
|
|
{
|
|
"stacktrace": {
|
|
"frames": [
|
|
{"filename": "some_system_file.py"},
|
|
{"filename": "another_system_file.py"},
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
hint = {}
|
|
|
|
result = sentry._filter_out_ignored_errors(event, hint)
|
|
|
|
assert result == event
|
|
|
|
def test_handles_malformed_exception_data(self, sentry_config):
|
|
sentry = TaskRunnerSentry(sentry_config)
|
|
|
|
test_cases = [
|
|
{},
|
|
{"exception": {"values": []}},
|
|
{"exception": {"values": [{"type": "ValueError"}]}},
|
|
{"exception": {"values": [{"stacktrace": {}}]}},
|
|
{"exception": {"values": [{"stacktrace": {"frames": []}}]}},
|
|
]
|
|
|
|
for event in test_cases:
|
|
result = sentry._filter_out_ignored_errors(event, {})
|
|
assert result == event
|
|
|
|
|
|
class TestSetupSentry:
|
|
def test_returns_none_when_disabled(self, disabled_sentry_config):
|
|
result = setup_sentry(disabled_sentry_config)
|
|
assert result is None
|
|
|
|
@patch("src.sentry.TaskRunnerSentry")
|
|
def test_initializes_sentry_when_enabled(self, mock_sentry_class, sentry_config):
|
|
mock_sentry = Mock()
|
|
mock_sentry_class.return_value = mock_sentry
|
|
|
|
result = setup_sentry(sentry_config)
|
|
|
|
mock_sentry_class.assert_called_once_with(sentry_config)
|
|
mock_sentry.init.assert_called_once()
|
|
assert result == mock_sentry
|
|
|
|
@patch("src.sentry.TaskRunnerSentry")
|
|
def test_handles_import_error(self, mock_sentry_class, sentry_config, caplog):
|
|
mock_sentry = Mock()
|
|
mock_sentry.init.side_effect = ImportError("sentry_sdk not found")
|
|
mock_sentry_class.return_value = mock_sentry
|
|
|
|
with caplog.at_level(logging.WARNING):
|
|
result = setup_sentry(sentry_config)
|
|
|
|
assert result is None
|
|
assert LOG_SENTRY_MISSING in caplog.text
|
|
|
|
@patch("src.sentry.TaskRunnerSentry")
|
|
def test_handles_general_exception(self, mock_sentry_class, sentry_config, caplog):
|
|
mock_sentry = Mock()
|
|
mock_sentry.init.side_effect = Exception("Something went wrong")
|
|
mock_sentry_class.return_value = mock_sentry
|
|
|
|
with caplog.at_level(logging.WARNING):
|
|
result = setup_sentry(sentry_config)
|
|
|
|
assert result is None
|
|
assert "Failed to initialize Sentry: Something went wrong" in caplog.text
|
|
|
|
|
|
class TestSentryConfig:
|
|
def test_enabled_returns_true_with_dsn(self, sentry_config):
|
|
assert sentry_config.enabled is True
|
|
|
|
def test_enabled_returns_false_without_dsn(self, disabled_sentry_config):
|
|
assert disabled_sentry_config.enabled is False
|
|
|
|
@patch.dict(
|
|
"os.environ",
|
|
{
|
|
"N8N_SENTRY_DSN": "https://test@sentry.io/789",
|
|
"N8N_VERSION": "2.0.0",
|
|
"ENVIRONMENT": "production",
|
|
"DEPLOYMENT_NAME": "prod-deployment",
|
|
},
|
|
)
|
|
def test_from_env_creates_config_from_environment(self):
|
|
config = SentryConfig.from_env()
|
|
|
|
assert config.dsn == "https://test@sentry.io/789"
|
|
assert config.n8n_version == "2.0.0"
|
|
assert config.environment == "production"
|
|
assert config.deployment_name == "prod-deployment"
|
|
|
|
@patch.dict("os.environ", {}, clear=True)
|
|
def test_from_env_uses_defaults_when_missing(self):
|
|
config = SentryConfig.from_env()
|
|
|
|
assert config.dsn == ""
|
|
assert config.n8n_version == ""
|
|
assert config.environment == ""
|
|
assert config.deployment_name == ""
|