7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
226 lines
7.5 KiB
Python
226 lines
7.5 KiB
Python
"""High-value tests for notifications/templates.py pure logic."""
|
|
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from local_deep_research.notifications.templates import (
|
|
EventType,
|
|
NotificationTemplate,
|
|
)
|
|
|
|
|
|
class TestEventType(unittest.TestCase):
|
|
def test_research_completed_value(self):
|
|
assert EventType.RESEARCH_COMPLETED.value == "research_completed"
|
|
|
|
def test_research_failed_value(self):
|
|
assert EventType.RESEARCH_FAILED.value == "research_failed"
|
|
|
|
def test_test_event_value(self):
|
|
assert EventType.TEST.value == "test"
|
|
|
|
def test_all_events_have_string_values(self):
|
|
for event in EventType:
|
|
assert isinstance(event.value, str)
|
|
|
|
def test_event_categories_present(self):
|
|
values = {e.value for e in EventType}
|
|
assert "research_completed" in values
|
|
assert "subscription_update" in values
|
|
assert "rate_limit_warning" in values
|
|
assert "test" in values
|
|
|
|
|
|
class TestFallbackTemplate(unittest.TestCase):
|
|
def test_returns_dict_with_title_and_body(self):
|
|
result = NotificationTemplate._get_fallback_template(
|
|
EventType.RESEARCH_COMPLETED, {"query": "test"}
|
|
)
|
|
assert "title" in result
|
|
assert "body" in result
|
|
|
|
def test_title_contains_event_name(self):
|
|
result = NotificationTemplate._get_fallback_template(
|
|
EventType.RESEARCH_COMPLETED, {}
|
|
)
|
|
assert "Research Completed" in result["title"]
|
|
|
|
def test_body_contains_context(self):
|
|
result = NotificationTemplate._get_fallback_template(
|
|
EventType.RESEARCH_FAILED, {"error": "timeout"}
|
|
)
|
|
assert "timeout" in result["body"]
|
|
|
|
def test_underscores_replaced(self):
|
|
result = NotificationTemplate._get_fallback_template(
|
|
EventType.SUBSCRIPTION_UPDATE, {}
|
|
)
|
|
assert "_" not in result["title"]
|
|
assert "Subscription Update" in result["title"]
|
|
|
|
def test_different_event_types(self):
|
|
for event_type in [
|
|
EventType.RESEARCH_QUEUED,
|
|
EventType.AUTH_ISSUE,
|
|
EventType.TEST,
|
|
]:
|
|
result = NotificationTemplate._get_fallback_template(event_type, {})
|
|
assert "title" in result
|
|
assert "body" in result
|
|
|
|
|
|
class TestFormatCustomTemplate(unittest.TestCase):
|
|
def setUp(self):
|
|
NotificationTemplate._jinja_env = None
|
|
|
|
def test_custom_template_with_context(self):
|
|
custom = {"title": "Hello {name}", "body": "Query: {query}"}
|
|
result = NotificationTemplate.format(
|
|
EventType.TEST,
|
|
{"name": "user", "query": "test"},
|
|
custom_template=custom,
|
|
)
|
|
assert result["title"] == "Hello user"
|
|
assert result["body"] == "Query: test"
|
|
|
|
def test_custom_template_missing_variable(self):
|
|
custom = {"title": "Hello {name}", "body": "Query: {query}"}
|
|
result = NotificationTemplate.format(
|
|
EventType.TEST, {"name": "user"}, custom_template=custom
|
|
)
|
|
# Missing {query} triggers KeyError, both title and body get error fallback
|
|
assert "Template error" in result["body"]
|
|
assert "Missing variable" in result["body"]
|
|
|
|
def test_custom_template_sanitizes_values(self):
|
|
custom = {"title": "{val}", "body": "ok"}
|
|
result = NotificationTemplate.format(
|
|
EventType.TEST, {"val": 42}, custom_template=custom
|
|
)
|
|
assert result["title"] == "42"
|
|
|
|
def test_custom_template_none_value_becomes_empty(self):
|
|
custom = {"title": "{val}", "body": "ok"}
|
|
result = NotificationTemplate.format(
|
|
EventType.TEST, {"val": None}, custom_template=custom
|
|
)
|
|
assert result["title"] == ""
|
|
|
|
|
|
class TestFormatUnknownEvent(unittest.TestCase):
|
|
def setUp(self):
|
|
NotificationTemplate._jinja_env = None
|
|
|
|
def test_unknown_event_not_in_template_files(self):
|
|
# RATE_LIMIT_WARNING is not in TEMPLATE_FILES
|
|
result = NotificationTemplate.format(
|
|
EventType.RATE_LIMIT_WARNING, {"detail": "slow"}
|
|
)
|
|
assert "title" in result
|
|
assert "body" in result
|
|
|
|
|
|
class TestFormatWithJinja(unittest.TestCase):
|
|
def setUp(self):
|
|
NotificationTemplate._jinja_env = None
|
|
|
|
def test_jinja_env_not_available_uses_fallback(self):
|
|
with patch.object(
|
|
NotificationTemplate, "_get_jinja_env", return_value=None
|
|
):
|
|
result = NotificationTemplate.format(
|
|
EventType.RESEARCH_COMPLETED, {"query": "test"}
|
|
)
|
|
assert "title" in result
|
|
assert "body" in result
|
|
|
|
def test_jinja_template_render_error_uses_fallback(self):
|
|
mock_env = MagicMock()
|
|
mock_template = MagicMock()
|
|
mock_template.render.side_effect = Exception("render error")
|
|
mock_env.get_template.return_value = mock_template
|
|
|
|
with patch.object(
|
|
NotificationTemplate, "_get_jinja_env", return_value=mock_env
|
|
):
|
|
result = NotificationTemplate.format(
|
|
EventType.RESEARCH_COMPLETED, {"query": "test"}
|
|
)
|
|
assert "title" in result
|
|
assert "body" in result
|
|
# Verify the render path was actually exercised
|
|
mock_template.render.assert_called_once()
|
|
|
|
def test_jinja_successful_render(self):
|
|
mock_env = MagicMock()
|
|
mock_template = MagicMock()
|
|
mock_template.render.return_value = (
|
|
"My Title\nMy body content\nMore body"
|
|
)
|
|
mock_env.get_template.return_value = mock_template
|
|
|
|
with patch.object(
|
|
NotificationTemplate, "_get_jinja_env", return_value=mock_env
|
|
):
|
|
result = NotificationTemplate.format(
|
|
EventType.RESEARCH_COMPLETED, {"query": "test"}
|
|
)
|
|
assert result["title"] == "My Title"
|
|
assert "My body content" in result["body"]
|
|
|
|
|
|
class TestGetRequiredContext(unittest.TestCase):
|
|
def setUp(self):
|
|
NotificationTemplate._jinja_env = None
|
|
|
|
def test_unknown_event_returns_empty(self):
|
|
result = NotificationTemplate.get_required_context(
|
|
EventType.RATE_LIMIT_WARNING
|
|
)
|
|
assert result == []
|
|
|
|
def test_no_jinja_env_returns_empty(self):
|
|
with patch.object(
|
|
NotificationTemplate, "_get_jinja_env", return_value=None
|
|
):
|
|
result = NotificationTemplate.get_required_context(
|
|
EventType.RESEARCH_COMPLETED
|
|
)
|
|
assert result == []
|
|
|
|
def test_returns_sorted_list(self):
|
|
mock_env = MagicMock()
|
|
mock_loader = MagicMock()
|
|
mock_loader.get_source.return_value = (
|
|
"{{ query }} {{ status }}",
|
|
None,
|
|
None,
|
|
)
|
|
mock_env.loader = mock_loader
|
|
mock_env.parse.return_value = MagicMock()
|
|
|
|
mock_template = MagicMock()
|
|
mock_template.environment = mock_env
|
|
mock_env.get_template.return_value = mock_template
|
|
|
|
import jinja2.meta # noqa: F401 - ensure submodule is loaded
|
|
|
|
with (
|
|
patch.object(
|
|
NotificationTemplate, "_get_jinja_env", return_value=mock_env
|
|
),
|
|
patch.object(
|
|
jinja2.meta,
|
|
"find_undeclared_variables",
|
|
return_value={"query", "status"},
|
|
),
|
|
):
|
|
result = NotificationTemplate.get_required_context(
|
|
EventType.RESEARCH_COMPLETED
|
|
)
|
|
assert result == ["query", "status"]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|