Compare commits

...

2 Commits

Author SHA1 Message Date
dgtlmoon
126c9864f8 test: unit tests for send_step_failure_notification AttributeError regression
Verifies that the module-level _check_cascading_vars is called correctly
(not as self._check_cascading_vars) and that a notification is queued
when step failure occurs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 11:39:19 +02:00
lawrence3699
01546cfbe4 fix: correct _check_cascading_vars call in send_step_failure_notification
_check_cascading_vars is a module-level function, but
send_step_failure_notification called it as self._check_cascading_vars()
and omitted the required datastore argument. This crashes with
AttributeError every time a browser step failure notification fires.

The sibling methods send_filter_failure_notification (line 450) and
send_content_changed_notification (lines 408-411) already use the
correct call pattern.
2026-04-10 11:50:41 +10:00
2 changed files with 69 additions and 1 deletions

View File

@@ -496,7 +496,7 @@ Thanks - Your omniscient changedetection.io installation.
n_object = NotificationContextData({
'notification_title': f"Changedetection.io - Alert - Browser step at position {step} could not be run",
'notification_body': body,
'notification_format': self._check_cascading_vars('notification_format', watch),
'notification_format': _check_cascading_vars(self.datastore, 'notification_format', watch),
})
n_object['markup_text_links_to_html_links'] = n_object.get('notification_format').startswith('html')

View File

@@ -0,0 +1,68 @@
"""
Unit test for send_step_failure_notification regression.
Before the fix, line 499 called self._check_cascading_vars('notification_format', watch)
which raises AttributeError because _check_cascading_vars is a module-level function,
not a method of NotificationService.
"""
import queue
from unittest.mock import MagicMock
def _make_datastore(watch_uuid, notification_url):
"""Minimal datastore mock that NotificationService and _check_cascading_vars need."""
watch = MagicMock()
watch.get = lambda key, default=None: {
'uuid': watch_uuid,
'url': 'https://example.com',
'notification_urls': [notification_url],
'notification_format': '',
'notification_muted': False,
}.get(key, default)
watch.__getitem__ = lambda self, key: watch.get(key)
datastore = MagicMock()
datastore.data = {
'watching': {watch_uuid: watch},
'settings': {
'application': {
'notification_urls': [],
'notification_format': 'text',
'filter_failure_notification_threshold_attempts': 3,
}
}
}
datastore.get_all_tags_for_watch.return_value = {}
return datastore, watch
def test_send_step_failure_notification_does_not_raise():
"""send_step_failure_notification must not raise AttributeError (wrong self. prefix on module-level function)."""
from changedetectionio.notification_service import NotificationService
watch_uuid = 'test-uuid-1234'
notification_q = queue.Queue()
datastore, _ = _make_datastore(watch_uuid, 'post://localhost/test')
service = NotificationService(datastore=datastore, notification_q=notification_q)
# Before the fix this raised:
# AttributeError: 'NotificationService' object has no attribute '_check_cascading_vars'
service.send_step_failure_notification(watch_uuid=watch_uuid, step_n=0)
def test_send_step_failure_notification_queues_item():
"""A notification object should be placed on the queue when URLs are configured."""
from changedetectionio.notification_service import NotificationService
watch_uuid = 'test-uuid-5678'
notification_q = queue.Queue()
datastore, _ = _make_datastore(watch_uuid, 'post://localhost/test')
service = NotificationService(datastore=datastore, notification_q=notification_q)
service.send_step_failure_notification(watch_uuid=watch_uuid, step_n=1)
assert not notification_q.empty(), "Expected a notification to be queued"
item = notification_q.get_nowait()
assert 'notification_title' in item
assert 'position 2' in item['notification_title']