refacot rtests
This commit is contained in:
parent
ae3d0966b1
commit
f3efee5add
5 changed files with 229 additions and 213 deletions
|
|
@ -29,6 +29,10 @@ logger = Logger.get()
|
|||
_VIEW_EXTRA_ARGS_FORMATTER = "--preset _view --overrides.url {}"
|
||||
|
||||
|
||||
def _log_time() -> str:
|
||||
return datetime.now().strftime("%Y-%m-%d-%H%M%S")
|
||||
|
||||
|
||||
def _maybe_write_subscription_log_file(
|
||||
config: ConfigFile,
|
||||
subscription: Subscription,
|
||||
|
|
@ -49,11 +53,10 @@ def _maybe_write_subscription_log_file(
|
|||
if success and not config.config_options.persist_logs.keep_successful_logs:
|
||||
return
|
||||
|
||||
log_time = datetime.now().strftime("%Y-%m-%d-%H%M%S")
|
||||
log_subscription_name = sanitize_filename(subscription.name).lower().replace(" ", "_")
|
||||
log_success = "success" if success else "error"
|
||||
|
||||
log_filename = f"{log_time}.{log_subscription_name}.{log_success}.log"
|
||||
log_filename = f"{_log_time()}.{log_subscription_name}.{log_success}.log"
|
||||
persist_log_path = Path(config.config_options.persist_logs.logs_directory) / log_filename
|
||||
|
||||
if not success:
|
||||
|
|
|
|||
87
tests/unit/cli/conftest.py
Normal file
87
tests/unit/cli/conftest.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
from typing import Callable
|
||||
from unittest.mock import patch
|
||||
|
||||
import mergedeep
|
||||
import pytest
|
||||
|
||||
from ytdl_sub.config.config_file import ConfigFile
|
||||
from ytdl_sub.subscriptions.subscription import Subscription
|
||||
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
|
||||
from ytdl_sub.utils.file_handler import FileMetadata
|
||||
from ytdl_sub.utils.logger import Logger
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subscription_download_factory():
|
||||
def _mock_subscription_download_factory(mock_success_output: bool) -> Callable:
|
||||
def _mock_download(self: Subscription, dry_run: bool) -> FileHandlerTransactionLog:
|
||||
Logger.get().info(
|
||||
"name=%s success=%s dry_run=%s", self.name, mock_success_output, dry_run
|
||||
)
|
||||
|
||||
if not mock_success_output:
|
||||
raise ValueError("error")
|
||||
|
||||
(
|
||||
self._enhanced_download_archive.get_file_handler_transaction_log()
|
||||
.log_created_file("created_file.txt", FileMetadata())
|
||||
.log_modified_file("modified_file.txt", FileMetadata())
|
||||
.log_removed_file("deleted_file.txt")
|
||||
)
|
||||
|
||||
return self._enhanced_download_archive.get_file_handler_transaction_log()
|
||||
|
||||
return _mock_download
|
||||
|
||||
return _mock_subscription_download_factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subscription_download_success(mock_subscription_download_factory: Callable):
|
||||
with patch.object(
|
||||
Subscription,
|
||||
"download",
|
||||
new=mock_subscription_download_factory(mock_success_output=True),
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def persist_logs_directory() -> str:
|
||||
# Delete the temp_dir on creation
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
pass
|
||||
|
||||
yield temp_dir
|
||||
|
||||
if os.path.isdir(temp_dir):
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def persist_logs_config_factory(
|
||||
music_video_config: ConfigFile, persist_logs_directory: str
|
||||
) -> Callable:
|
||||
def _persist_logs_config_factory(keep_successful_logs: bool) -> ConfigFile:
|
||||
return ConfigFile.from_dict(
|
||||
dict(
|
||||
mergedeep.merge(
|
||||
music_video_config.as_dict(),
|
||||
{
|
||||
"configuration": {
|
||||
"persist_logs": {
|
||||
"logs_directory": persist_logs_directory,
|
||||
"keep_successful_logs": keep_successful_logs,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return _persist_logs_config_factory
|
||||
|
|
@ -1,113 +1,24 @@
|
|||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import patch
|
||||
|
||||
import mergedeep
|
||||
import pytest
|
||||
from conftest import assert_logs
|
||||
|
||||
from ytdl_sub.cli.entrypoint import _download_subscriptions_from_yaml_files
|
||||
from ytdl_sub.cli.entrypoint import main
|
||||
from ytdl_sub.cli.output_summary import output_summary
|
||||
from ytdl_sub.cli.output_transaction_log import logger as transaction_logger
|
||||
from ytdl_sub.config.config_file import ConfigFile
|
||||
from ytdl_sub.subscriptions.subscription import Subscription
|
||||
from ytdl_sub.utils.exceptions import ExperimentalFeatureNotEnabled
|
||||
from ytdl_sub.utils.file_handler import FileHandler
|
||||
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
|
||||
from ytdl_sub.utils.file_handler import FileMetadata
|
||||
from ytdl_sub.utils.logger import Logger
|
||||
|
||||
####################################################################################################
|
||||
# SHARED FIXTURES
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subscription_download_factory():
|
||||
def _mock_subscription_download_factory(mock_success_output: bool) -> Callable:
|
||||
def _mock_download(self: Subscription, dry_run: bool) -> FileHandlerTransactionLog:
|
||||
Logger.get().info(
|
||||
"name=%s success=%s dry_run=%s", self.name, mock_success_output, dry_run
|
||||
)
|
||||
time.sleep(1)
|
||||
if not mock_success_output:
|
||||
raise ValueError("error")
|
||||
|
||||
(
|
||||
self._enhanced_download_archive.get_file_handler_transaction_log()
|
||||
.log_created_file("created_file.txt", FileMetadata())
|
||||
.log_modified_file("modified_file.txt", FileMetadata())
|
||||
.log_removed_file("deleted_file.txt")
|
||||
)
|
||||
|
||||
return self._enhanced_download_archive.get_file_handler_transaction_log()
|
||||
|
||||
return _mock_download
|
||||
|
||||
return _mock_subscription_download_factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subscription_download_success(mock_subscription_download_factory: Callable):
|
||||
with patch.object(
|
||||
Subscription,
|
||||
"download",
|
||||
new=mock_subscription_download_factory(mock_success_output=True),
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
####################################################################################################
|
||||
# PERSIST LOGS FIXTURES + TESTS
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def persist_logs_directory() -> str:
|
||||
# Delete the temp_dir on creation
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
pass
|
||||
|
||||
yield temp_dir
|
||||
|
||||
if os.path.isdir(temp_dir):
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def persist_logs_config_factory(
|
||||
music_video_config: ConfigFile, persist_logs_directory: str
|
||||
) -> Callable:
|
||||
def _persist_logs_config_factory(keep_successful_logs: bool) -> ConfigFile:
|
||||
return ConfigFile.from_dict(
|
||||
dict(
|
||||
mergedeep.merge(
|
||||
music_video_config.as_dict(),
|
||||
{
|
||||
"configuration": {
|
||||
"persist_logs": {
|
||||
"logs_directory": persist_logs_directory,
|
||||
"keep_successful_logs": keep_successful_logs,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return _persist_logs_config_factory
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dry_run", [True, False])
|
||||
@pytest.mark.parametrize("mock_success_output", [True, False])
|
||||
@pytest.mark.parametrize("keep_successful_logs", [True, False])
|
||||
|
|
@ -128,7 +39,8 @@ def test_subscription_logs_write_to_file(
|
|||
Subscription,
|
||||
"download",
|
||||
new=mock_subscription_download_factory(mock_success_output=mock_success_output),
|
||||
):
|
||||
# mock datetime to be an index to be able to run instantly
|
||||
), patch("ytdl_sub.cli.entrypoint._log_time", side_effect=[str(idx) for idx in range(10)]):
|
||||
try:
|
||||
_download_subscriptions_from_yaml_files(
|
||||
config=config,
|
||||
|
|
@ -149,7 +61,7 @@ def test_subscription_logs_write_to_file(
|
|||
elif not mock_success_output:
|
||||
assert len(log_directory_files) == 2
|
||||
for log_path in log_directory_files:
|
||||
assert bool(re.match(r"\d{4}-\d{2}-\d{2}-\d{6}\.john_smith\.error\.log", log_path.name))
|
||||
assert bool(re.match(r"\d\.john_smith\.error\.log", log_path.name))
|
||||
with open(log_path, "r", encoding="utf-8") as log_file:
|
||||
assert log_file.readlines()[-1] == (
|
||||
f"Please upload the error log file '{str(log_path)}' and make a Github issue "
|
||||
|
|
@ -160,9 +72,7 @@ def test_subscription_logs_write_to_file(
|
|||
else:
|
||||
assert len(log_directory_files) == num_subscriptions
|
||||
for log_file_path in log_directory_files:
|
||||
assert bool(
|
||||
re.match(r"\d{4}-\d{2}-\d{2}-\d{6}\.john_smith\.success\.log", log_file_path.name)
|
||||
)
|
||||
assert bool(re.match(r"\d\.john_smith\.success\.log", log_file_path.name))
|
||||
with open(log_file_path, "r", encoding="utf-8") as log_file:
|
||||
assert (
|
||||
log_file.readlines()[-1]
|
||||
|
|
@ -170,123 +80,6 @@ def test_subscription_logs_write_to_file(
|
|||
)
|
||||
|
||||
|
||||
####################################################################################################
|
||||
# TRANSACTION LOGS FIXTURES + TESTS
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def transaction_log_file_path() -> str:
|
||||
# Delete the temp_file on creation
|
||||
with tempfile.NamedTemporaryFile() as temp_file:
|
||||
pass
|
||||
|
||||
yield temp_file.name
|
||||
|
||||
if os.path.isfile(temp_file.name):
|
||||
FileHandler.delete(temp_file.name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("file_transaction_log", [None, "output.log"])
|
||||
def test_suppress_transaction_log(
|
||||
mock_subscription_download_success,
|
||||
music_video_config_path: Path,
|
||||
music_video_subscription_path: Path,
|
||||
file_transaction_log: Optional[str],
|
||||
) -> None:
|
||||
with patch.object(
|
||||
sys,
|
||||
"argv",
|
||||
[
|
||||
"ytdl-sub",
|
||||
"--config",
|
||||
str(music_video_config_path),
|
||||
"sub",
|
||||
str(music_video_subscription_path),
|
||||
"--suppress-transaction-log",
|
||||
]
|
||||
+ (["--transaction-log", file_transaction_log] if file_transaction_log else []),
|
||||
), patch("ytdl_sub.cli.output_transaction_log.output_transaction_log") as mock_transaction_log:
|
||||
transaction_logs = main()
|
||||
|
||||
assert transaction_logs
|
||||
assert mock_transaction_log.call_count == 0
|
||||
|
||||
|
||||
def test_transaction_log_to_file(
|
||||
mock_subscription_download_success,
|
||||
music_video_config_path: Path,
|
||||
music_video_subscription_path: Path,
|
||||
transaction_log_file_path: Path,
|
||||
) -> None:
|
||||
with patch.object(
|
||||
sys,
|
||||
"argv",
|
||||
[
|
||||
"ytdl-sub",
|
||||
"--config",
|
||||
str(music_video_config_path),
|
||||
"sub",
|
||||
str(music_video_subscription_path),
|
||||
"--transaction-log",
|
||||
str(transaction_log_file_path),
|
||||
],
|
||||
):
|
||||
subscriptions = main()
|
||||
assert subscriptions
|
||||
|
||||
with open(transaction_log_file_path, "r", encoding="utf-8") as transaction_log_file:
|
||||
assert transaction_log_file.readlines()[0] == "Transaction log for john_smith:\n"
|
||||
|
||||
|
||||
def test_transaction_log_to_logger(
|
||||
mock_subscription_download_success,
|
||||
music_video_config_path: Path,
|
||||
music_video_subscription_path: Path,
|
||||
) -> None:
|
||||
with patch.object(
|
||||
sys,
|
||||
"argv",
|
||||
[
|
||||
"ytdl-sub",
|
||||
"--config",
|
||||
str(music_video_config_path),
|
||||
"sub",
|
||||
str(music_video_subscription_path),
|
||||
],
|
||||
), assert_logs(
|
||||
logger=transaction_logger,
|
||||
expected_message="Transaction log for john_smith:\n",
|
||||
log_level="info",
|
||||
):
|
||||
transaction_logs = main()
|
||||
assert transaction_logs
|
||||
|
||||
|
||||
def test_output_summary():
|
||||
subscription_values: List[Tuple[str, int, int, int, int, Optional[Exception]]] = [
|
||||
("long_name_but_lil_values", 0, 0, 0, 6, None),
|
||||
("john_smith", 1, 0, 0, 52, None),
|
||||
("david_gore", 0, 0, 0, 4, None),
|
||||
("christopher_snoop", 50, 0, 3, 518, None),
|
||||
("beyond funk", 0, 0, 0, 176, ValueError("lol")),
|
||||
]
|
||||
|
||||
mock_subscriptions: List[MagicMock] = []
|
||||
for values in subscription_values:
|
||||
sub = Mock()
|
||||
sub.name = values[0]
|
||||
sub.num_entries_added = values[1]
|
||||
sub.num_entries_modified = values[2]
|
||||
sub.num_entries_removed = values[3]
|
||||
sub.num_entries = values[4]
|
||||
sub.exception = values[5]
|
||||
|
||||
mock_subscriptions.append(sub)
|
||||
|
||||
_ = output_summary(subscriptions=mock_subscriptions)
|
||||
assert True # Test used for manual inspection - too hard to test ansi color codes
|
||||
|
||||
|
||||
def test_update_with_info_json_requires_experimental_flag(
|
||||
music_video_config_path: Path,
|
||||
music_video_subscription_path: Path,
|
||||
|
|
|
|||
32
tests/unit/cli/test_output_summary.py
Normal file
32
tests/unit/cli/test_output_summary.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import Mock
|
||||
|
||||
from ytdl_sub.cli.output_summary import output_summary
|
||||
|
||||
|
||||
def test_output_summary_one_error():
|
||||
subscription_values: List[Tuple[str, int, int, int, int, Optional[Exception]]] = [
|
||||
("long_name_but_lil_values", 0, 0, 0, 6, None),
|
||||
("john_smith", 1, 0, 0, 52, None),
|
||||
("david_gore", 0, 0, 0, 4, None),
|
||||
("christopher_snoop", 50, 0, 3, 518, None),
|
||||
("beyond funk", 0, 0, 0, 176, ValueError("lol")),
|
||||
]
|
||||
|
||||
mock_subscriptions: List[MagicMock] = []
|
||||
for values in subscription_values:
|
||||
sub = Mock()
|
||||
sub.name = values[0]
|
||||
sub.num_entries_added = values[1]
|
||||
sub.num_entries_modified = values[2]
|
||||
sub.num_entries_removed = values[3]
|
||||
sub.num_entries = values[4]
|
||||
sub.exception = values[5]
|
||||
|
||||
mock_subscriptions.append(sub)
|
||||
|
||||
_ = output_summary(subscriptions=mock_subscriptions)
|
||||
assert True # Test used for manual inspection - too hard to test ansi color codes
|
||||
101
tests/unit/cli/test_output_transaction_log.py
Normal file
101
tests/unit/cli/test_output_transaction_log.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from conftest import assert_logs
|
||||
|
||||
from ytdl_sub.cli.entrypoint import main
|
||||
from ytdl_sub.cli.output_transaction_log import logger as transaction_logger
|
||||
from ytdl_sub.utils.file_handler import FileHandler
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def transaction_log_file_path() -> str:
|
||||
# Delete the temp_file on creation
|
||||
with tempfile.NamedTemporaryFile() as temp_file:
|
||||
pass
|
||||
|
||||
yield temp_file.name
|
||||
|
||||
if os.path.isfile(temp_file.name):
|
||||
FileHandler.delete(temp_file.name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("file_transaction_log", [None, "output.log"])
|
||||
def test_suppress_transaction_log(
|
||||
mock_subscription_download_success,
|
||||
music_video_config_path: Path,
|
||||
music_video_subscription_path: Path,
|
||||
file_transaction_log: Optional[str],
|
||||
) -> None:
|
||||
with patch.object(
|
||||
sys,
|
||||
"argv",
|
||||
[
|
||||
"ytdl-sub",
|
||||
"--config",
|
||||
str(music_video_config_path),
|
||||
"sub",
|
||||
str(music_video_subscription_path),
|
||||
"--suppress-transaction-log",
|
||||
]
|
||||
+ (["--transaction-log", file_transaction_log] if file_transaction_log else []),
|
||||
), patch("ytdl_sub.cli.output_transaction_log.output_transaction_log") as mock_transaction_log:
|
||||
transaction_logs = main()
|
||||
|
||||
assert transaction_logs
|
||||
assert mock_transaction_log.call_count == 0
|
||||
|
||||
|
||||
def test_transaction_log_to_file(
|
||||
mock_subscription_download_success,
|
||||
music_video_config_path: Path,
|
||||
music_video_subscription_path: Path,
|
||||
transaction_log_file_path: Path,
|
||||
) -> None:
|
||||
with patch.object(
|
||||
sys,
|
||||
"argv",
|
||||
[
|
||||
"ytdl-sub",
|
||||
"--config",
|
||||
str(music_video_config_path),
|
||||
"sub",
|
||||
str(music_video_subscription_path),
|
||||
"--transaction-log",
|
||||
str(transaction_log_file_path),
|
||||
],
|
||||
):
|
||||
subscriptions = main()
|
||||
assert subscriptions
|
||||
|
||||
with open(transaction_log_file_path, "r", encoding="utf-8") as transaction_log_file:
|
||||
assert transaction_log_file.readlines()[0] == "Transaction log for john_smith:\n"
|
||||
|
||||
|
||||
def test_transaction_log_to_logger(
|
||||
mock_subscription_download_success,
|
||||
music_video_config_path: Path,
|
||||
music_video_subscription_path: Path,
|
||||
) -> None:
|
||||
with patch.object(
|
||||
sys,
|
||||
"argv",
|
||||
[
|
||||
"ytdl-sub",
|
||||
"--config",
|
||||
str(music_video_config_path),
|
||||
"sub",
|
||||
str(music_video_subscription_path),
|
||||
],
|
||||
), assert_logs(
|
||||
logger=transaction_logger,
|
||||
expected_message="Transaction log for john_smith:\n",
|
||||
log_level="info",
|
||||
):
|
||||
transaction_logs = main()
|
||||
assert transaction_logs
|
||||
Loading…
Reference in a new issue