test: audit suite for meaningfulness, reach 100% coverage
The suite had 8 empty `pass` tests, two tests that made live network calls to a production healthcheck endpoint, and a write_csv test that only asserted the file existed. CI ran only test_komodo_deploy, so the main suite never executed at all. - Split tests by module: test_qlist, test_qprocess, test_qlogging_extra, test_apprise, test_qbt; rewrote test_qbitmaid and test_write_csv. - qbt_fakes.py: shared fakes (FakeQbt, FakeQbtClient, FakeAPIError, StubPushover, FailingPushover, torrent factory). - test_qbt.py loads the hyphenated qbit-maid.py via importlib and covers the __main__ entry point. - Replaced the live-network tests with a RecordingRequests stub (suite time 14s -> 0.035s). - .coveragerc + CI unit-tests job running the full suite with --fail-under=100. 127 unit tests, 100% statement coverage on all 5 first-party modules (440 stmts), 15/15 mutations caught. Seven pre-existing bugs are pinned by tests with KNOWN BUG docstrings and left unfixed pending a decision.
This commit is contained in:
1 parent
5b54d9e558
commit
925f9619be
10 files changed
+1645
-461
No files matched your search
+173
@@ -0,0 +1,173 @@
|
||||
"""Tests for AppriseClient — the HTTP poster and its config resolution."""
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import AppriseClient as apprise_module
|
||||
from AppriseClient import AppriseClient, apprise_notify
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
status_code = 200
|
||||
|
||||
|
||||
class FakeRequests:
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
|
||||
def post(self, url, json=None, verify=None):
|
||||
self.calls.append({"url": url, "json": json, "verify": verify})
|
||||
return FakeResponse()
|
||||
|
||||
|
||||
class TestAppriseNotify(unittest.TestCase):
|
||||
def test_posts_the_expected_payload(self):
|
||||
req = FakeRequests()
|
||||
response = apprise_notify(
|
||||
req, "apprise.local", 8088, "mailto://u:p@example.com", "Title", "Body"
|
||||
)
|
||||
self.assertIsInstance(response, FakeResponse)
|
||||
self.assertEqual(len(req.calls), 1)
|
||||
call = req.calls[0]
|
||||
self.assertEqual(call["url"], "http://apprise.local:8088/notify/")
|
||||
self.assertEqual(call["json"], {
|
||||
"urls": "mailto://u:p@example.com",
|
||||
"title": "Title",
|
||||
"body": "Body",
|
||||
})
|
||||
self.assertFalse(call["verify"])
|
||||
|
||||
|
||||
class TestAppriseClientConfig(unittest.TestCase):
|
||||
ENV_KEYS = ("DOCKER", "host", "port", "aurls", "title", "body", "toml_path")
|
||||
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.mkdtemp()
|
||||
self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True)
|
||||
self._cwd = os.getcwd()
|
||||
os.chdir(self.tmp)
|
||||
self.addCleanup(os.chdir, self._cwd)
|
||||
|
||||
saved = {key: os.environ.get(key) for key in self.ENV_KEYS}
|
||||
for key in self.ENV_KEYS:
|
||||
os.environ.pop(key, None)
|
||||
self.addCleanup(self._restore_env, saved)
|
||||
|
||||
self.captured = {}
|
||||
patcher = mock.patch.object(
|
||||
apprise_module, "apprise_notify", self._fake_notify
|
||||
)
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def _restore_env(self, saved):
|
||||
for key, value in saved.items():
|
||||
if value is None:
|
||||
os.environ.pop(key, None)
|
||||
else:
|
||||
os.environ[key] = value
|
||||
|
||||
def _fake_notify(self, req_obj, host, port, aurls, title, body):
|
||||
self.captured.update(host=host, port=port, aurls=aurls, title=title, body=body)
|
||||
return "sent"
|
||||
|
||||
def write_config(self, **overrides):
|
||||
values = {
|
||||
"host": "config-host",
|
||||
"port": 9999,
|
||||
"aurls": "mailto://config@example.com",
|
||||
"title": "Config Title",
|
||||
"body": "Config Body",
|
||||
}
|
||||
values.update(overrides)
|
||||
with open("config.toml", "w", encoding="utf-8") as fh:
|
||||
fh.write(
|
||||
"[apprise]\n"
|
||||
f'host = "{values["host"]}"\n'
|
||||
f"port = {values["port"]}\n"
|
||||
f'aurls = "{values["aurls"]}"\n'
|
||||
f'title = "{values["title"]}"\n'
|
||||
f'body = "{values["body"]}"\n'
|
||||
)
|
||||
|
||||
def test_reads_values_from_environment(self):
|
||||
os.environ.update({
|
||||
"DOCKER": "1",
|
||||
"host": "env-host",
|
||||
"port": "1234",
|
||||
"aurls": "mailto://env@example.com",
|
||||
"title": "Env Title",
|
||||
"body": "Env Body",
|
||||
})
|
||||
AppriseClient()
|
||||
self.assertEqual(self.captured["host"], "env-host")
|
||||
self.assertEqual(self.captured["port"], "1234")
|
||||
self.assertEqual(self.captured["title"], "Env Title")
|
||||
|
||||
def test_reads_values_from_config_file(self):
|
||||
self.write_config()
|
||||
AppriseClient()
|
||||
self.assertEqual(self.captured["host"], "config-host")
|
||||
self.assertEqual(self.captured["port"], 9999)
|
||||
self.assertEqual(self.captured["aurls"], "mailto://config@example.com")
|
||||
self.assertEqual(self.captured["title"], "Config Title")
|
||||
self.assertEqual(self.captured["body"], "Config Body")
|
||||
|
||||
def test_config_file_overrides_environment(self):
|
||||
os.environ.update({
|
||||
"DOCKER": "1",
|
||||
"host": "env-host",
|
||||
"port": "1234",
|
||||
"aurls": "mailto://env@example.com",
|
||||
"title": "Env Title",
|
||||
"body": "Env Body",
|
||||
})
|
||||
self.write_config()
|
||||
AppriseClient()
|
||||
self.assertEqual(self.captured["host"], "config-host")
|
||||
self.assertEqual(self.captured["title"], "Config Title")
|
||||
|
||||
def _write_elsewhere_config(self):
|
||||
config_path = os.path.join(self.tmp, "elsewhere.toml")
|
||||
with open(config_path, "w", encoding="utf-8") as fh:
|
||||
fh.write(
|
||||
"[apprise]\n"
|
||||
'host = "toml-host"\n'
|
||||
"port = 4321\n"
|
||||
'aurls = "mailto://toml@example.com"\n'
|
||||
'title = "Toml Title"\n'
|
||||
'body = "Toml Body"\n'
|
||||
)
|
||||
return config_path
|
||||
|
||||
def test_toml_path_is_read_when_docker_is_set(self):
|
||||
# The DOCKER branch reads five env vars before toml_path is reached, so
|
||||
# all of them must be present for the toml_path branch to run at all.
|
||||
os.environ.update({
|
||||
"DOCKER": "1",
|
||||
"host": "env-host",
|
||||
"port": "1234",
|
||||
"aurls": "mailto://env@example.com",
|
||||
"title": "Env Title",
|
||||
"body": "Env Body",
|
||||
})
|
||||
os.environ["toml_path"] = self._write_elsewhere_config()
|
||||
AppriseClient()
|
||||
self.assertEqual(self.captured["host"], "toml-host")
|
||||
self.assertEqual(self.captured["port"], 4321)
|
||||
|
||||
def test_toml_path_is_ignored_without_docker(self):
|
||||
"""KNOWN BUG: the bare ``except`` swallows the DOCKER KeyError and
|
||||
skips the rest of the try block, so ``toml_path`` is never read unless
|
||||
``DOCKER`` is also set. With no ./config.toml either, the client then
|
||||
crashes on the missing ``host`` attribute.
|
||||
"""
|
||||
os.environ["toml_path"] = self._write_elsewhere_config()
|
||||
with self.assertRaises(AttributeError):
|
||||
AppriseClient()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user