diff --git a/changedetectionio/flask_app.py b/changedetectionio/flask_app.py index 02e041b1..d5ea2460 100644 --- a/changedetectionio/flask_app.py +++ b/changedetectionio/flask_app.py @@ -679,7 +679,10 @@ def changedetection_app(config=None, datastore_o=None): if request.method == 'POST' and form.validate(): - extra_update_obj = {} + extra_update_obj = { + 'consecutive_filter_failures': 0, + 'last_error' : False + } if request.args.get('unpause_on_save'): extra_update_obj['paused'] = False diff --git a/changedetectionio/model/App.py b/changedetectionio/model/App.py index e412542b..fdd627ed 100644 --- a/changedetectionio/model/App.py +++ b/changedetectionio/model/App.py @@ -5,6 +5,7 @@ from changedetectionio.notification import ( default_notification_title, ) +# Equal to or greater than this number of FilterNotFoundInResponse exceptions will trigger a filter-not-found notification _FILTER_FAILURE_THRESHOLD_ATTEMPTS_DEFAULT = 6 DEFAULT_SETTINGS_HEADERS_USERAGENT='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36' diff --git a/changedetectionio/tests/test_filter_failure_notification.py b/changedetectionio/tests/test_filter_failure_notification.py index b25931ea..6d4c3154 100644 --- a/changedetectionio/tests/test_filter_failure_notification.py +++ b/changedetectionio/tests/test_filter_failure_notification.py @@ -21,10 +21,11 @@ def set_response_with_filter(): f.write(test_return_data) return None -def run_filter_test(client, content_filter): +def run_filter_test(client, live_server, content_filter): + + # Response WITHOUT the filter ID element + set_original_response() - # Give the endpoint time to spin up - time.sleep(1) # cleanup for the next client.get( url_for("form_delete", uuid="all"), @@ -79,6 +80,7 @@ def run_filter_test(client, content_filter): "include_filters": content_filter, "fetch_backend": "html_requests"}) + # A POST here will also reset the filter failure counter (filter_failure_notification_threshold_attempts) res = client.post( url_for("edit_page", uuid="first"), data=notification_form_data, @@ -91,20 +93,21 @@ def run_filter_test(client, content_filter): # Now the notification should not exist, because we didnt reach the threshold assert not os.path.isfile("test-datastore/notification.txt") - # -2 because we would have checked twice above (on adding and on edit) + # recheck it up to just before the threshold, including the fact that in the previous POST it would have rechecked (and incremented) for i in range(0, App._FILTER_FAILURE_THRESHOLD_ATTEMPTS_DEFAULT-2): - res = client.get(url_for("form_watch_checknow"), follow_redirects=True) + client.get(url_for("form_watch_checknow"), follow_redirects=True) wait_for_all_checks(client) - assert not os.path.isfile("test-datastore/notification.txt"), f"test-datastore/notification.txt should not exist - Attempt {i}" + time.sleep(2) # delay for apprise to fire + assert not os.path.isfile("test-datastore/notification.txt"), f"test-datastore/notification.txt should not exist - Attempt {i} when threshold is {App._FILTER_FAILURE_THRESHOLD_ATTEMPTS_DEFAULT}" # We should see something in the frontend + res = client.get(url_for("index")) assert b'Warning, no filters were found' in res.data - # One more check should trigger it (see -2 above) - client.get(url_for("form_watch_checknow"), follow_redirects=True) - wait_for_all_checks(client) + # One more check should trigger the _FILTER_FAILURE_THRESHOLD_ATTEMPTS_DEFAULT threshold client.get(url_for("form_watch_checknow"), follow_redirects=True) wait_for_all_checks(client) + time.sleep(2) # delay for apprise to fire # Now it should exist and contain our "filter not found" alert assert os.path.isfile("test-datastore/notification.txt") @@ -149,13 +152,9 @@ def test_setup(live_server): live_server_setup(live_server) def test_check_include_filters_failure_notification(client, live_server): - set_original_response() - wait_for_all_checks(client) - run_filter_test(client, '#nope-doesnt-exist') + run_filter_test(client, live_server,'#nope-doesnt-exist') def test_check_xpath_filter_failure_notification(client, live_server): - set_original_response() - time.sleep(1) - run_filter_test(client, '//*[@id="nope-doesnt-exist"]') + run_filter_test(client, live_server, '//*[@id="nope-doesnt-exist"]') # Test that notification is never sent diff --git a/changedetectionio/update_worker.py b/changedetectionio/update_worker.py index dcb9dbe7..f17d3a28 100644 --- a/changedetectionio/update_worker.py +++ b/changedetectionio/update_worker.py @@ -348,7 +348,7 @@ class update_worker(threading.Thread): # Send notification if we reached the threshold? threshold = self.datastore.data['settings']['application'].get('filter_failure_notification_threshold_attempts', 0) - logger.error(f"Filter for {uuid} not found, consecutive_filter_failures: {c}") + logger.warning(f"Filter for {uuid} not found, consecutive_filter_failures: {c}") if threshold > 0 and c >= threshold: if not self.datastore.data['watching'][uuid].get('notification_muted'): self.send_filter_failure_notification(uuid) @@ -362,7 +362,6 @@ class update_worker(threading.Thread): # Yes fine, so nothing todo, don't continue to process. process_changedetection_results = False changed_detected = False - self.datastore.update_watch(uuid=uuid, update_obj={'last_error': False}) except content_fetchers.exceptions.BrowserConnectError as e: self.datastore.update_watch(uuid=uuid, update_obj={'last_error': e.msg