fix: resolve the seven bugs pinned by the test audit
test / unit-tests (push) Successful in 14s
ci / docker (push) Successful in 23s
test / unit-tests (pull_request) Successful in 6s
test / komodo-deploy-script (push) Successful in 23s
test / komodo-deploy-script (pull_request) Successful in 16s

- read [ignored_tags] instead of [ignored_domains], so tag-based ignoring
  actually works
- match ignored tags as whole tags, not substrings ('ipt' no longer matches
  'iptorrents')
- send the login-error notification through the Pushover client instead of
  the non-existent module-level send_message()
- handle an empty torrent list instead of raising IndexError before any
  summary is sent
- tag a new torrent once instead of once per code path
- honour toml_path without DOCKER in AppriseClient
- close replaced log handlers so the file descriptor is released

Also add the missing [telemetry] section to config.toml.example, which
qbit-maid.py requires at startup.

The tests that pinned each bug now assert the fixed behaviour, and the
suite still reports 100% coverage.
This commit is contained in:
trex Bot committed 2026-11-03 16:10:19 -06:00
1 parent ab46ea1fb2
commit d8034278d8
10 files changed
+207 -92

No files matched your search

+43 -9
View File
@@ -5,10 +5,9 @@ import os
import shutil
import tempfile
import unittest
import warnings
from collections import Counter
from qbt_fakes import FakeQbt, StubPushover, torrent
from qbt_fakes import FailingPushover, FakeQbt, StubPushover, torrent
from qlogging import (
LOGGER_NAME,
debug_torrent_list,
@@ -24,6 +23,7 @@ from qlogging import (
summary_runtime,
summary_stats,
tor_notify,
tor_notify_error,
tor_notify_summary,
tornotifytest,
torrent_count,
@@ -76,6 +76,35 @@ class TestNotifySetup(unittest.TestCase):
self.assertEqual(qbt.poc.calls[0]["kwargs"]["title"], "qbit-maid")
class TestTorNotifyError(unittest.TestCase):
def test_sends_through_the_pushover_client(self):
qbt = FakeQbt(use_pushover=True)
tor_notify_error(qbt, RuntimeError("boom"))
self.assertEqual(len(qbt.poc.calls), 1)
self.assertEqual(qbt.poc.calls[0]["user"], "USERKEY")
self.assertIn("boom", qbt.poc.calls[0]["body"])
self.assertEqual(qbt.poc.calls[0]["kwargs"]["title"],
"qbit-maid API ERROR")
def test_skips_pushover_when_disabled(self):
qbt = FakeQbt(use_pushover=False)
tor_notify_error(qbt, RuntimeError("boom"))
self.assertEqual(qbt.poc.calls, [])
def test_skips_pushover_when_no_client_was_built(self):
qbt = FakeQbt(use_pushover=True, poc=None)
tor_notify_error(qbt, RuntimeError("boom")) # must not raise
def test_survives_a_failing_pushover(self):
qbt = FakeQbt(use_pushover=True, poc=FailingPushover())
tor_notify_error(qbt, RuntimeError("boom")) # must not raise
def test_accepts_a_custom_title(self):
qbt = FakeQbt(use_pushover=True)
tor_notify_error(qbt, RuntimeError("boom"), title="Custom")
self.assertEqual(qbt.poc.calls[0]["kwargs"]["title"], "Custom")
class TestCounters(unittest.TestCase):
def test_process_counts_buckets_by_tag(self):
qbt = FakeQbt(
@@ -117,6 +146,12 @@ class TestDebugHelpers(unittest.TestCase):
list_first_tor(qbt, index=1)
self.assertIn("One", "\n".join(captured.output))
def test_list_first_tor_handles_an_empty_list(self):
qbt = FakeQbt(torrent_list=[])
with self.assertLogs("qbit-maid-test", level="DEBUG") as captured:
list_first_tor(qbt)
self.assertIn("No torrents found", "\n".join(captured.output))
def test_list_qbit_api_info_logs_versions(self):
qbt = FakeQbt()
with self.assertLogs("qbit-maid-test", level="DEBUG") as captured:
@@ -233,14 +268,13 @@ class TestSetupLogging(unittest.TestCase):
self.assertEqual(logger.level, logging.INFO)
def test_setup_is_idempotent(self):
setup_logging(True, self.log_path, "INFO")
# setup_logging drops the previous handlers without closing them, so a
# second call leaks the old file descriptor. Known issue; silenced here
# so the suite output stays readable.
with warnings.catch_warnings():
warnings.simplefilter("ignore", ResourceWarning)
logger = setup_logging(True, self.log_path, "INFO")
first = setup_logging(True, self.log_path, "INFO")
previous_file_handler = first.handlers[0]
self.assertIsNotNone(previous_file_handler.stream)
logger = setup_logging(True, self.log_path, "INFO")
self.assertEqual(len(logger.handlers), 2)
# The replaced handler is closed, so its file descriptor is released.
self.assertIsNone(previous_file_handler.stream)
def test_rotation_parameters_are_applied(self):
logger = setup_logging(True, self.log_path, "INFO",