Skip to content

Commit

Permalink
Update lots of tests
Browse files Browse the repository at this point in the history
  • Loading branch information
MrTyton committed Sep 26, 2024
1 parent dc8587a commit 7f37060
Show file tree
Hide file tree
Showing 9 changed files with 640 additions and 41 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ wheels/
*.egg-info/
.installed.cfg
*.egg
.vscode/

# PyInstaller
# Usually these files are written by a python script from a template
Expand Down
224 changes: 224 additions & 0 deletions root/app/calibredb_utils_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import unittest
from unittest.mock import MagicMock, patch
from parameterized import parameterized
from typing import NamedTuple, Optional

import fanfic_info
import calibre_info
from calibredb_utils import (
call_calibre_db,
export_story,
remove_story,
add_story,
)


class CallCalibreDbTestCase(unittest.TestCase):
class CallCalibreDbParams(NamedTuple):
command: str
calibre_info: calibre_info.CalibreInfo
fanfic_info: Optional[fanfic_info.FanficInfo]
expected_command: str
should_raise_exception: bool

@parameterized.expand(
[
CallCalibreDbParams(
command="list",
calibre_info=MagicMock(),
fanfic_info=None,
expected_command="list ",
should_raise_exception=False,
),
CallCalibreDbParams(
command="add",
calibre_info=MagicMock(),
fanfic_info=MagicMock(calibre_id="123"),
expected_command="add 123",
should_raise_exception=False,
),
CallCalibreDbParams(
command="remove",
calibre_info=MagicMock(),
fanfic_info=MagicMock(calibre_id="123"),
expected_command="remove 123",
should_raise_exception=True,
),
]
)
@patch("calibredb_utils.call", return_value=None)
@patch("calibredb_utils.ff_logging.log_failure")
@patch("calibredb_utils.ff_logging.log_debug")
def test_call_calibre_db(
self,
command,
calibre_info,
fanfic_info,
expected_command,
should_raise_exception,
mock_log_debug,
mock_log_failure,
mock_call,
):
calibre_info.lock = MagicMock()

if should_raise_exception:
mock_call.side_effect = Exception("Test exception")

call_calibre_db(command, calibre_info, fanfic_info)

mock_log_debug.assert_called_once_with(
f'\tCalling calibredb with command: \t"{expected_command} {calibre_info}"'
)

if should_raise_exception:
mock_log_failure.assert_called_once()
else:
mock_log_failure.assert_not_called()


class ExportStoryTestCase(unittest.TestCase):
class ExportStoryParams(NamedTuple):
fanfic_info: fanfic_info.FanficInfo
location: str
calibre_info: calibre_info.CalibreInfo
expected_command: str

@parameterized.expand(
[
ExportStoryParams(
fanfic_info=MagicMock(),
location="/fake/location",
calibre_info=MagicMock(),
expected_command='export --dont-save-cover --dont-write-opf --single-dir --to-dir "/fake/location"',
),
]
)
@patch("calibredb_utils.call_calibre_db")
def test_export_story(
self,
fanfic_info,
location,
calibre_info,
expected_command,
mock_call_calibre_db,
):
export_story(
fanfic_info=fanfic_info,
location=location,
calibre_info=calibre_info,
)
mock_call_calibre_db.assert_called_once_with(
expected_command, calibre_info, fanfic_info
)


class RemoveStoryTestCase(unittest.TestCase):
class RemoveStoryParams(NamedTuple):
fanfic_info: fanfic_info.FanficInfo
calibre_info: calibre_info.CalibreInfo
expected_command: str

@parameterized.expand(
[
RemoveStoryParams(
fanfic_info=MagicMock(calibre_id="123"),
calibre_info=MagicMock(),
expected_command="remove",
),
]
)
@patch("calibredb_utils.call_calibre_db")
def test_remove_story(
self,
fanfic_info,
calibre_info,
expected_command,
mock_call_calibre_db,
):
remove_story(fanfic_info, calibre_info)
mock_call_calibre_db.assert_called_once_with(
expected_command, calibre_info, fanfic_info
)


class AddStoryTestCase(unittest.TestCase):
class AddStoryParams(NamedTuple):
location: str
fanfic_info: fanfic_info.FanficInfo
calibre_info: calibre_info.CalibreInfo
epub_files: list
expected_command: str
should_fail: bool

@parameterized.expand(
[
AddStoryParams(
location="/fake/location",
fanfic_info=MagicMock(),
calibre_info=MagicMock(return_value="mock_calibre_info"),
epub_files=["/fake/location/story.epub"],
expected_command='add -d {mock} "/fake/location/story.epub"',
should_fail=False,
),
AddStoryParams(
location="/fake/location",
fanfic_info=MagicMock(),
calibre_info=MagicMock(return_value="mock_calibre_info"),
epub_files=[],
expected_command="",
should_fail=True,
),
]
)
@patch("calibredb_utils.system_utils.get_files")
@patch(
"calibredb_utils.regex_parsing.extract_filename",
return_value="Story Title",
)
@patch("calibredb_utils.call_calibre_db")
@patch("calibredb_utils.ff_logging.log_failure")
@patch("calibredb_utils.ff_logging.log")
def test_add_story(
self,
location,
fanfic_info,
calibre_info,
epub_files,
expected_command,
should_fail,
mock_log,
mock_log_failure,
mock_call_calibre_db,
mock_extract_filename,
mock_get_files,
):
mock_get_files.return_value = epub_files
calibre_info.return_value = "mock_calibre_info"

add_story(
location=location,
fanfic_info=fanfic_info,
calibre_info=calibre_info,
)

if should_fail:
mock_log_failure.assert_called_once_with(
"No EPUB files found in the specified location."
)
mock_call_calibre_db.assert_not_called()
else:
mock_log.assert_called_once_with(
f"\t({fanfic_info.site}) Adding {epub_files[0]} to Calibre",
"OKGREEN",
)
mock_call_calibre_db.assert_called_once_with(
expected_command.format(mock=calibre_info),
calibre_info,
fanfic_info=None,
)
self.assertEqual(fanfic_info.title, "Story Title")


if __name__ == "__main__":
unittest.main()
47 changes: 43 additions & 4 deletions root/app/fanfic_info_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from unittest.mock import Mock, patch, mock_open, MagicMock
from fanfic_info import FanficInfo


class TestFanficInfo(unittest.TestCase):
def setUp(self):
self.fanfic_info = FanficInfo(
Expand All @@ -25,13 +26,19 @@ def test_reached_maximum_repeats(self):
@patch("fanfic_info.check_output")
@patch("builtins.open", new_callable=mock_open)
@patch("fanfic_info.ff_logging.log")
def test_get_id_from_calibredb(self, mock_ff_logger, mock_open, mock_check_output):
def test_get_id_from_calibredb(
self, mock_ff_logger, mock_open, mock_check_output
):
mock_check_output.return_value = b"1234"
calibre_information = Mock()
calibre_information.lock = MagicMock()
self.assertTrue(self.fanfic_info.get_id_from_calibredb(calibre_information))
self.assertTrue(
self.fanfic_info.get_id_from_calibredb(calibre_information)
)
self.assertEqual(self.fanfic_info.calibre_id, "1234")
mock_ff_logger.assert_called_once_with("\t(ffnet) Story is in Calibre with Story ID: 1234", "OKBLUE")
mock_ff_logger.assert_called_once_with(
"\t(ffnet) Story is in Calibre with Story ID: 1234", "OKBLUE"
)

def test_eq(self):
other_fanfic_info = FanficInfo(
Expand All @@ -45,5 +52,37 @@ def test_eq(self):
)
self.assertTrue(self.fanfic_info == other_fanfic_info)

def test_hash(self):
fanfic_info_1 = FanficInfo(
url="https://www.fanfiction.net/s/1234",
site="ffnet",
calibre_id="1234",
repeats=0,
max_repeats=10,
behavior="update",
title="Test Story",
)
fanfic_info_2 = FanficInfo(
url="https://www.fanfiction.net/s/1234",
site="ffnet",
calibre_id="1234",
repeats=0,
max_repeats=10,
behavior="update",
title="Test Story",
)
fanfic_info_3 = FanficInfo(
url="https://www.fanfiction.net/s/5678",
site="ffnet",
calibre_id="5678",
repeats=0,
max_repeats=10,
behavior="update",
title="Another Story",
)
self.assertEqual(hash(fanfic_info_1), hash(fanfic_info_2))
self.assertNotEqual(hash(fanfic_info_1), hash(fanfic_info_3))


if __name__ == "__main__":
unittest.main()
unittest.main()
12 changes: 7 additions & 5 deletions root/app/notification_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
import tomllib
from typing import Callable, Any

kSleepTime = 10
kMaxAttempts = 3


class NotificationBase:
def __init__(self, toml_path: str):
def __init__(self, toml_path: str, sleep_time: int = 10) -> None:
"""
Initializes the NotificationBase class.
"""
Expand Down Expand Up @@ -57,11 +60,10 @@ def retry_decorator(func: Callable) -> Callable:
"""

def wrapper(*args: Any, **kwargs: Any) -> None:
attempts = 3
for attempt in range(attempts):
for attempt in range(kMaxAttempts):
if func(*args, **kwargs):
return
else:
time.sleep(10 * (attempt + 1))
elif attempt < kMaxAttempts - 1:
time.sleep(kSleepTime * (attempt + 1))

return wrapper
61 changes: 61 additions & 0 deletions root/app/notification_base_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import unittest
from unittest.mock import patch, MagicMock
from notification_base import (
NotificationBase,
retry_decorator,
kSleepTime,
kMaxAttempts,
)
import tomllib


class TestNotificationBase(unittest.TestCase):
def setUp(self):
patcher_open = patch("builtins.open", new_callable=MagicMock)
patcher_toml_load = patch("tomllib.load", return_value={"key": "value"})

self.mock_open = patcher_open.start()
self.mock_toml_load = patcher_toml_load.start()

self.addCleanup(patcher_open.stop)
self.addCleanup(patcher_toml_load.stop)

self.notification = NotificationBase("fake_path")

def test_initialization(self):
# Test initialization and config loading
self.mock_open.assert_called_once_with("fake_path", "rb")
self.mock_toml_load.assert_called_once()
self.assertFalse(self.notification.enabled)
self.assertEqual(self.notification.config, {"key": "value"})

def test_send_notification_not_implemented(self):
# Test that send_notification raises NotImplementedError
with self.assertRaises(NotImplementedError):
self.notification.send_notification("title", "body", "site")


class TestRetryDecorator(unittest.TestCase):
@patch("time.sleep", return_value=None)
def test_retry_decorator_success(self, mock_sleep):
# Test that the decorated function is called once if it succeeds
mock_func = MagicMock(return_value=True)
decorated_func = retry_decorator(mock_func)
decorated_func()
mock_func.assert_called_once()
mock_sleep.assert_not_called()

@patch("time.sleep", return_value=None)
def test_retry_decorator_failure(self, mock_sleep):
# Test that the decorated function is retried up to 3 times if it fails
mock_func = MagicMock(return_value=False)
decorated_func = retry_decorator(mock_func)
decorated_func()
self.assertEqual(mock_func.call_count, kMaxAttempts)
self.assertEqual(mock_sleep.call_count, kMaxAttempts - 1)
for i in range(kMaxAttempts - 1):
mock_sleep.assert_any_call(kSleepTime * (i + 1))


if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit 7f37060

Please sign in to comment.