From 5961f4e65effe6861e2b2669181ab94a1ae80589 Mon Sep 17 00:00:00 2001 From: Ismo Vuorinen Date: Wed, 30 Apr 2025 16:42:10 +0300 Subject: [PATCH] chore(lint): fixed pyright errors, tests --- aeonview.py | 47 ++-- aeonview_test.py | 575 ++++++++++++++++++++--------------------------- requirements.txt | 1 + 3 files changed, 274 insertions(+), 349 deletions(-) diff --git a/aeonview.py b/aeonview.py index 75b811c..a6e87ed 100644 --- a/aeonview.py +++ b/aeonview.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import argparse import hashlib import logging @@ -41,6 +43,7 @@ class AeonViewImages: self.project_path = project_path or None self.url = url or None self.args = args or {} + self.simulate = getattr(args, "simulate", False) def get_image_paths( self, url: str | None, destination_base: Path | None, date: datetime @@ -86,7 +89,7 @@ class AeonViewImages: destination_file = AeonViewHelpers.build_path(destination, file_name) if not destination.exists(): - if self.args.get("simulate", False): + if getattr(self.args, "simulate", False): logging.info("Simulate: would create %s", destination) else: AeonViewHelpers.mkdir_p(destination) @@ -116,7 +119,7 @@ class AeonViewImages: Download the image from the URL and save it to the project directory. """ - date_param = self.args.get("date", None) + date_param = getattr(self.args, "date", None) if date_param is not None: try: @@ -143,7 +146,7 @@ class AeonViewImages: logging.info("Saving image to %s", dest_file) - if not self.args.get("simulate", False): + if not self.simulate: AeonViewHelpers.mkdir_p(dest_dir) self.download_image(dest_file) else: @@ -167,10 +170,7 @@ class AeonViewImages: logging.error("Invalid destination path.") sys.exit(1) - if ( - self.args.get("simulate", False) is False - or self.args.get("simulate", None) is None - ): + if not getattr(self.args, "simulate", False): logging.info("Downloading image from %s", self.url) response = requests.get(self.url, stream=True, timeout=10) if response.status_code == 200: @@ -210,14 +210,11 @@ class AeonViewVideos: self.project_path = project_path self.args = args - self.args["simulate"] = ( - args.get("simulate", False) if isinstance(args, dict) else False - ) - self.args["fps"] = args.get("fps", 10) if isinstance(args, dict) else 10 - - self.day = args.get("day", None) if args else None - self.month = args.get("month", None) if args else None - self.year = args.get("year", None) if args else None + self.simulate = getattr(args, "simulate", False) + self.fps = getattr(args, "fps", 10) + self.day = getattr(args, "day", None) + self.month = getattr(args, "month", None) + self.year = getattr(args, "year", None) self.path_images = AeonViewHelpers.build_path(self.project_path, "img") self.path_videos = AeonViewHelpers.build_path(self.project_path, "vid") @@ -235,13 +232,13 @@ class AeonViewVideos: output_dir = AeonViewHelpers.build_path(self.path_videos, year_month) output_file = AeonViewHelpers.build_path(output_dir, f"{self.day}.mp4") ffmpeg_cmd = AeonViewHelpers.generate_ffmpeg_command( - input_dir, output_file, self.args.fps + input_dir, output_file, self.fps ) logging.info("Generating video from %s", input_dir) logging.info("Output file will be %s", output_file) - if not self.args.get("simulate", False): + if not self.simulate: logging.info("Running ffmpeg command: %s", " ".join(ffmpeg_cmd)) if not os.path.exists(input_dir): AeonViewHelpers.mkdir_p(output_dir) @@ -418,7 +415,15 @@ class AeonViewApp: """ def __init__(self): - self.args, self.parser = AeonViewHelpers.parse_arguments() + args, parser = AeonViewHelpers.parse_arguments() + self.args: argparse.Namespace = args + self.parser: argparse.ArgumentParser = parser + + if self.args is None: + logging.error("No arguments provided.") + self.parser.print_help() + sys.exit(1) + AeonViewHelpers.setup_logger(self.args.verbose) self.base_path = Path(self.args.dest).resolve() @@ -480,6 +485,8 @@ class AeonViewApp: logging.error("Project path %s does not exist.", project_path) sys.exit(1) + generate_date = None + try: generate_date = ( datetime.strptime(self.args.generate, "%Y-%m-%d") @@ -502,7 +509,9 @@ class AeonViewApp: logging.error("Invalid date: %s-%s-%s", year, month, day) sys.exit(1) - avm = AeonViewVideos(project_path, vars(self.args)) + args: argparse.Namespace = self.args + + avm = AeonViewVideos(project_path, args) if self.args.timeframe == "daily": avm.generate_daily_video() diff --git a/aeonview_test.py b/aeonview_test.py index 9a6d3fe..9beaa4a 100644 --- a/aeonview_test.py +++ b/aeonview_test.py @@ -5,7 +5,7 @@ import subprocess import tempfile from datetime import datetime from pathlib import Path -from unittest import TestCase, mock +from unittest import mock import pytest @@ -16,7 +16,6 @@ from aeonview import ( AeonViewVideos, ) -# Define values used in the tests default_dest = str(Path.cwd() / "projects") default_project = "default" default_fps = 10 @@ -28,348 +27,264 @@ default_test_path = Path("/tmp/test_project").resolve() tmp_images = Path("/tmp/images") -# Define the Helpers class with methods to be tested -class TestHelpers(TestCase): - def test_check_date_valid(self): - self.assertTrue(AeonViewHelpers.check_date(2023, 12, 31)) - - def test_check_date_invalid(self): - self.assertFalse(AeonViewHelpers.check_date(2023, 2, 30)) - - def test_mkdir_p_creates_directory(self): - with tempfile.TemporaryDirectory() as tmp: - test_path = Path(tmp) / "a" / "b" / "c" - AeonViewHelpers.mkdir_p(test_path) - self.assertTrue(test_path.exists()) - self.assertTrue(test_path.is_dir()) - - def test_get_extension_valid(self): - prefix = default_image_domain - types = ["jpg", "png", "gif", "webp"] - for ext in types: - self.assertEqual( - AeonViewHelpers.get_extension(f"{prefix}.{ext}"), - f".{ext}", - ) - - def test_get_extension_invalid(self): - self.assertEqual( - AeonViewHelpers.get_extension(default_image_domain), ".jpg" - ) # Default behavior - self.assertIsNone(AeonViewHelpers.get_extension(None)) +def test_check_date_valid(): + assert AeonViewHelpers.check_date(2023, 12, 31) -class TestFFmpegCommand(TestCase): - def test_generate_ffmpeg_command(self): - input_dir = tmp_images - output_file = Path("/tmp/output.mp4") - fps = 24 - cmd = AeonViewHelpers.generate_ffmpeg_command( - input_dir, output_file, fps - ) - self.assertIn("ffmpeg", cmd[0]) - self.assertIn(str(fps), cmd) - self.assertEqual(str(output_file), cmd[-1]) - self.assertIn(str(input_dir / "*.{jpg,jpeg,png,gif,webp}"), cmd) - - def test_generate_ffmpeg_command_output_format(self): - input_dir = tmp_images - output_file = Path("/tmp/video.mp4") - cmd = AeonViewHelpers.generate_ffmpeg_command( - input_dir, output_file, 30 - ) - self.assertIn("/tmp/images/*.{jpg,jpeg,png,gif,webp}", cmd) - self.assertIn("/tmp/video.mp4", cmd) - self.assertIn("-c:v", cmd) - self.assertIn("libx264", cmd) - self.assertIn("-pix_fmt", cmd) - self.assertIn("yuv420p", cmd) - - @mock.patch("subprocess.run") - def test_simulate_ffmpeg_call(self, mock_run): - input_dir = tmp_images - output_file = Path("/tmp/out.mp4") - cmd = AeonViewHelpers.generate_ffmpeg_command( - input_dir, output_file, 10 - ) - subprocess.run(cmd, check=True) - mock_run.assert_called_once_with(cmd) +def test_check_date_invalid(): + assert not AeonViewHelpers.check_date(2023, 2, 30) -class TestAeonViewImages(TestCase): - def setUp(self): - self.args = argparse.Namespace() - self.args.simulate = False - self.args.date = "2025-04-10 12:30:45" - self.args.url = f"{default_image_domain}.jpg" - self.args.dest = default_test_path - self.args.project = default_project - self.args.verbose = default_verbose - self.args.fps = default_fps - self.args.timeframe = default_timeframe - self.project_path = default_test_path - self.url = f"{default_image_domain}.jpg" +def test_mkdir_p_creates_directory(): + with tempfile.TemporaryDirectory() as tmp: + test_path = Path(tmp) / "a" / "b" / "c" + AeonViewHelpers.mkdir_p(test_path) + assert test_path.exists() + assert test_path.is_dir() - def test_get_image_paths_valid(self): - url = f"{default_image_domain}.jpg" - destination_base = default_test_path - date = datetime(2025, 4, 10, 12, 30, 45) - aeon_view_images = AeonViewImages(destination_base, url) - paths = aeon_view_images.get_image_paths(url, destination_base, date) - self.assertEqual(paths["url"], url) - self.assertEqual(paths["file"], "12-30-45.jpg") - self.assertEqual( - paths["destinations"]["file"], - destination_base / "2025-04" / "10" / "12-30-45.jpg", - ) - def test_get_image_paths_invalid_url(self): - with pytest.raises(SystemExit): - with self.assertLogs(level="ERROR") as log: - aeon_view_images = AeonViewImages( - default_test_path, "invalid-url" - ) - aeon_view_images.get_image_paths( - "invalid-url", - default_test_path, - datetime(2025, 4, 10), - ) - self.assertIn(AeonViewMessages.INVALID_URL, log.output[0]) +@pytest.mark.parametrize("ext", ["jpg", "png", "gif", "webp"]) +def test_get_extension_valid(ext): + assert AeonViewHelpers.get_extension(f"{default_image_domain}.{ext}") == f".{ext}" - def test_get_image_paths_invalid_date(self): - with pytest.raises(SystemExit): - with self.assertLogs(level="ERROR") as log: - aeon_view_images = AeonViewImages( - default_test_path, f"{default_image_domain}.jpg" - ) - aeon_view_images.get_image_paths( - f"{default_image_domain}.jpg", - default_test_path, - "invalid-date", - ) - self.assertIn(AeonViewMessages.INVALID_DATE, log.output[0]) - @mock.patch("aeonview.AeonViewHelpers.mkdir_p") - @mock.patch("aeonview.AeonViewImages.download_image") - def test_get_current_image(self, mock_download_image, mock_mkdir_p): - project_path = default_test_path - url = f"{default_image_domain}.jpg" - args = argparse.Namespace(simulate=False, date="2025-04-10 12:30:45") - avi = AeonViewImages(project_path, url, args) +def test_get_extension_invalid(): + assert AeonViewHelpers.get_extension(default_image_domain) == ".jpg" + assert AeonViewHelpers.get_extension(None) is None + + +def test_generate_ffmpeg_command(): + input_dir = tmp_images + output_file = Path("/tmp/output.mp4") + fps = 24 + cmd = AeonViewHelpers.generate_ffmpeg_command(input_dir, output_file, fps) + assert "ffmpeg" in cmd[0] + assert str(fps) in cmd + assert str(output_file) == cmd[-1] + assert str(input_dir / "*.{jpg,jpeg,png,gif,webp}") in cmd + + +def test_generate_ffmpeg_command_output_format(): + input_dir = tmp_images + output_file = Path("/tmp/video.mp4") + cmd = AeonViewHelpers.generate_ffmpeg_command(input_dir, output_file, 30) + assert "/tmp/images/*.{jpg,jpeg,png,gif,webp}" in cmd + assert "/tmp/video.mp4" in cmd + assert "-c:v" in cmd + assert "libx264" in cmd + assert "-pix_fmt" in cmd + assert "yuv420p" in cmd + + +@mock.patch("subprocess.run") +def test_simulate_ffmpeg_call(mock_run): + input_dir = tmp_images + output_file = Path("/tmp/out.mp4") + cmd = AeonViewHelpers.generate_ffmpeg_command(input_dir, output_file, 10) + subprocess.run(cmd, check=True) + mock_run.assert_called_once_with(cmd, check=True) + + +def test_get_image_paths_valid(): + url = f"{default_image_domain}.jpg" + destination_base = default_test_path + date = datetime(2025, 4, 10, 12, 30, 45) + aeon_view_images = AeonViewImages(destination_base, url) + paths = aeon_view_images.get_image_paths(url, destination_base, date) + assert paths["url"] == url + assert paths["file"] == "12-30-45.jpg" + assert paths["destinations"]["file"] == destination_base / "2025-04" / "10" / "12-30-45.jpg" + + +def test_get_image_paths_invalid_url(): + with pytest.raises(SystemExit), mock.patch("aeonview.logging.error") as log: + aeon_view_images = AeonViewImages(default_test_path, "invalid-url") + aeon_view_images.get_image_paths("invalid-url", default_test_path, datetime(2025, 4, 10)) + assert AeonViewMessages.INVALID_URL in log.call_args[0][0] + + +def test_get_image_paths_invalid_date(): + with pytest.raises(SystemExit), mock.patch("aeonview.logging.error") as log: + aeon_view_images = AeonViewImages(default_test_path, f"{default_image_domain}.jpg") + aeon_view_images.get_image_paths(f"{default_image_domain}.jpg", default_test_path, "invalid-date") # pyright: ignore [reportArgumentType] + assert AeonViewMessages.INVALID_DATE in log.call_args[0][0] + + +@mock.patch("aeonview.AeonViewHelpers.mkdir_p") +@mock.patch("aeonview.AeonViewImages.download_image") +def test_get_current_image(mock_download_image, mock_mkdir_p): + args = argparse.Namespace(simulate=False, date="2025-04-10 12:30:45") + avi = AeonViewImages(default_test_path, f"{default_image_domain}.jpg", args) + avi.get_current_image() + mock_mkdir_p.assert_called() + mock_download_image.assert_called() + + +@mock.patch("aeonview.requests.get") +def test_download_image_success(mock_get): + mock_response = mock.Mock() + mock_response.status_code = 200 + mock_response.iter_content = mock.Mock(return_value=[b"data"]) + mock_get.return_value = mock_response + + args = argparse.Namespace(simulate=False) + avi = AeonViewImages(default_test_path, f"{default_image_domain}.jpg", args) + with tempfile.NamedTemporaryFile(delete=True) as temp_file: + destination = Path(temp_file.name) + avi.download_image(destination) + mock_get.assert_called_once_with(f"{default_image_domain}.jpg", stream=True, timeout=10) + assert destination.exists() + + +@mock.patch("aeonview.requests.get") +def test_download_image_failure(mock_get): + mock_response = mock.Mock() + mock_response.status_code = 404 + mock_get.return_value = mock_response + + args = argparse.Namespace(simulate=False) + avi = AeonViewImages(default_test_path, f"{default_image_domain}.jpg", args) + destination = Path("/tmp/image.jpg") + + with pytest.raises(SystemExit), mock.patch("aeonview.logging.error") as log: + avi.download_image(destination) + assert AeonViewMessages.DOWNLOAD_FAILURE in log.call_args[0][0] + + +@mock.patch("aeonview.AeonViewHelpers.mkdir_p") +@mock.patch("subprocess.run") +def test_generate_daily_video(mock_subprocess_run, mock_mkdir_p): + args = argparse.Namespace(simulate=False, fps=10, day="01", month="04", year="2025") + avv = AeonViewVideos(default_test_path, args) + with mock.patch("aeonview.logging.info") as log: + avv.generate_daily_video() + last_call_args = log.call_args_list[-1][0] + assert last_call_args[0] == "%s: %s" + assert last_call_args[1] == AeonViewMessages.VIDEO_GENERATION_SUCCESS + assert last_call_args[2] == default_test_path / "vid/2025-04/01.mp4" + + mock_mkdir_p.assert_called() + mock_subprocess_run.assert_called() + + +@mock.patch("aeonview.AeonViewHelpers.mkdir_p") +def test_generate_daily_video_simulate(mock_mkdir_p): + args = argparse.Namespace(simulate=True, fps=10, day="01", month="04", year="2025") + avv = AeonViewVideos(default_test_path, args) + avv.generate_daily_video() + mock_mkdir_p.assert_not_called() + + +def test_generate_monthly_video_not_implemented(): + args = argparse.Namespace(simulate=False, fps=10, day="01", month="04", year="2025") + avv = AeonViewVideos(default_test_path, args) + with pytest.raises(NotImplementedError): + avv.generate_monthly_video(Path("/tmp")) + + +def test_generate_yearly_video_not_implemented(): + args = argparse.Namespace(simulate=False, fps=10, day="01", month="04", year="2025") + avv = AeonViewVideos(default_test_path, args) + with pytest.raises(NotImplementedError): + avv.generate_yearly_video(Path("/tmp")) + + +@mock.patch("sys.argv", ["aeonview.py", "--mode", "image", "--url", f"{default_image_domain}.jpg"]) +def test_parse_arguments_image_mode(): + args, _ = AeonViewHelpers.parse_arguments() + assert args.mode == "image" + assert args.url == f"{default_image_domain}.jpg" + assert args.dest == default_dest + + +@mock.patch("sys.argv", ["aeonview.py", "--mode", "video", "--project", f"{default_project}"]) +def test_parse_arguments_video_mode(): + args, _ = AeonViewHelpers.parse_arguments() + assert args.mode == "video" + assert args.project == default_project + assert args.dest == default_dest + + +@mock.patch("sys.argv", ["aeonview.py", "--mode", "image", "--simulate"]) +def test_parse_arguments_simulate_mode(): + args, _ = AeonViewHelpers.parse_arguments() + assert args.mode == "image" + assert args.simulate + + +@mock.patch("sys.argv", ["aeonview.py", "--mode", "video", "--fps", "30"]) +def test_parse_arguments_fps(): + args, _ = AeonViewHelpers.parse_arguments() + assert args.mode == "video" + assert args.project == default_project + assert args.dest == default_dest + assert args.fps == 30 + + +@mock.patch("sys.argv", ["aeonview.py", "--mode", "video", "--generate", "2023-10-01"]) +def test_parse_arguments_generate_date(): + args, _ = AeonViewHelpers.parse_arguments() + assert args.mode == "video" + assert args.generate == "2023-10-01" + + +@mock.patch("sys.argv", ["aeonview.py", "--mode", "image", "--verbose"]) +def test_parse_arguments_verbose(): + args, _ = AeonViewHelpers.parse_arguments() + assert args.mode == "image" + assert args.verbose + + +@mock.patch("sys.argv", ["aeonview.py"]) +def test_parse_arguments_defaults(): + args, _ = AeonViewHelpers.parse_arguments() + assert args.mode == "image" + assert args.project == default_project + assert args.dest == default_dest + assert args.fps == 10 + assert args.timeframe == "daily" + assert not args.simulate + assert not args.verbose + + +@mock.patch("aeonview.AeonViewHelpers.mkdir_p") +@mock.patch("aeonview.AeonViewImages.download_image") +def test_image_simulation(mock_download_image, mock_mkdir_p): + args = mock.MagicMock() + args.simulate = True + args.date = "2025-04-10 12:30:45" + avi = AeonViewImages(default_test_path, f"{default_image_domain}.jpg", args) + with mock.patch("aeonview.logging.info") as log: avi.get_current_image() - mock_mkdir_p.assert_called() - mock_download_image.assert_called() - - @mock.patch("aeonview.requests.get") - def test_download_image_success(self, mock_get): - mock_response = mock.Mock() - mock_response.status_code = 200 - mock_response.iter_content = mock.Mock(return_value=[b"data"]) - mock_get.return_value = mock_response - - project_path = default_test_path - url = f"{default_image_domain}.jpg" - args = argparse.Namespace(simulate=False) - avi = AeonViewImages(project_path, url, args) - with tempfile.NamedTemporaryFile(delete=True) as temp_file: - destination = Path(temp_file.name) - avi.download_image(destination) - - mock_get.assert_called_once_with(url, stream=True) - self.assertTrue(destination.exists()) - - @mock.patch("aeonview.requests.get") - def test_download_image_failure(self, mock_get): - mock_response = mock.Mock() - mock_response.status_code = 404 - mock_get.return_value = mock_response - - project_path = default_test_path - url = f"{default_image_domain}.jpg" - args = argparse.Namespace(simulate=False) - avi = AeonViewImages(project_path, url, args) - destination = Path("/tmp/image.jpg") - - with pytest.raises(SystemExit): - with self.assertLogs(level="ERROR") as log: - avi.download_image(destination) - self.assertIn(AeonViewMessages.DOWNLOAD_FAILURE, log.output[0]) + mock_mkdir_p.assert_not_called() + mock_download_image.assert_not_called() + assert any("Saving image to" in str(call) for call in log.call_args_list) -class TestAeonViewVideos(TestCase): - @mock.patch("aeonview.AeonViewHelpers.mkdir_p") - @mock.patch("subprocess.run") - def test_generate_daily_video(self, mock_subprocess_run, mock_mkdir_p): - project_path = default_test_path - args = argparse.Namespace( - simulate=False, fps=10, day="01", month="04", year="2025" - ) - avv = AeonViewVideos(project_path, args) - with self.assertLogs(level="INFO") as log: - avv.generate_daily_video() - msg = AeonViewMessages.VIDEO_GENERATION_SUCCESS - expected_message = ( - f"{msg}: {default_test_path / 'vid/2025-04/01.mp4'}" - ) - self.assertIn( - expected_message, log.output[-1] - ) # Ensure it's the last log entry - mock_mkdir_p.assert_called() - mock_subprocess_run.assert_called() - - @mock.patch("aeonview.AeonViewHelpers.mkdir_p") - def test_generate_daily_video_simulate(self, mock_mkdir_p): - project_path = default_test_path - args = argparse.Namespace( - simulate=True, fps=10, day="01", month="04", year="2025" - ) - avv = AeonViewVideos(project_path, args) +@mock.patch("aeonview.AeonViewHelpers.mkdir_p") +@mock.patch("subprocess.run") +def test_video_simulation(mock_subprocess_run, mock_mkdir_p): + args = mock.MagicMock() + args.simulate = True + args.fps = 10 + args.day = "01" + args.month = "01" + args.year = "2023" + avv = AeonViewVideos(default_test_path, args) + with mock.patch("aeonview.logging.info") as log: avv.generate_daily_video() mock_mkdir_p.assert_not_called() - - def test_generate_monthly_video_not_implemented(self): - project_path = default_test_path - args = argparse.Namespace( - simulate=False, fps=10, day="01", month="04", year="2025" - ) - avv = AeonViewVideos(project_path, args) - with pytest.raises(NotImplementedError): - avv.generate_monthly_video(Path("/tmp")) - - def test_generate_yearly_video_not_implemented(self): - project_path = default_test_path - args = argparse.Namespace( - simulate=False, fps=10, day="01", month="04", year="2025" - ) - avv = AeonViewVideos(project_path, args) - with pytest.raises(NotImplementedError): - avv.generate_yearly_video(Path("/tmp")) + mock_subprocess_run.assert_not_called() + assert any("Generating video from" in str(call) for call in log.call_args_list) -class TestHelpersArguments(TestCase): - def setUp(self): - self.default_dest = str(Path.cwd() / "projects") - - @mock.patch( - "sys.argv", - [ - "aeonview.py", - "--mode", - "image", - "--url", - f"{default_image_domain}.jpg", - ], - ) - def test_parse_arguments_image_mode(self): - args, _ = AeonViewHelpers.parse_arguments() - self.assertEqual(args.mode, "image") - self.assertEqual(args.url, f"{default_image_domain}.jpg") - self.assertEqual(args.dest, self.default_dest) - - @mock.patch( - "sys.argv", - ["aeonview.py", "--mode", "video", "--project", f"{default_project}"], - ) - def test_parse_arguments_video_mode(self): - args, _ = AeonViewHelpers.parse_arguments() - self.assertEqual(args.mode, "video") - self.assertEqual(args.project, f"{default_project}") - self.assertEqual(args.dest, self.default_dest) - - @mock.patch("sys.argv", ["aeonview.py", "--mode", "image", "--simulate"]) - def test_parse_arguments_simulate_mode(self): - args, _ = AeonViewHelpers.parse_arguments() - self.assertEqual(args.mode, "image") - self.assertTrue(args.simulate) - - @mock.patch("sys.argv", ["aeonview.py", "--mode", "video", "--fps", "30"]) - def test_parse_arguments_fps(self): - args, _ = AeonViewHelpers.parse_arguments() - self.assertEqual(args.mode, "video") - self.assertEqual(args.project, f"{default_project}") - self.assertEqual(args.dest, self.default_dest) - self.assertEqual(args.fps, 30) - - @mock.patch( - "sys.argv", - ["aeonview.py", "--mode", "video", "--generate", "2023-10-01"], - ) - def test_parse_arguments_generate_date(self): - args, _ = AeonViewHelpers.parse_arguments() - self.assertEqual(args.mode, "video") - self.assertEqual(args.generate, "2023-10-01") - - @mock.patch("sys.argv", ["aeonview.py", "--mode", "image", "--verbose"]) - def test_parse_arguments_verbose(self): - args, _ = AeonViewHelpers.parse_arguments() - self.assertEqual(args.mode, "image") - self.assertTrue(args.verbose) - - @mock.patch("sys.argv", ["aeonview.py"]) - def test_parse_arguments_defaults(self): - args, _ = AeonViewHelpers.parse_arguments() - self.assertEqual(args.mode, "image") - self.assertEqual(args.project, f"{default_project}") - self.assertEqual(args.dest, self.default_dest) - self.assertEqual(args.fps, 10) - self.assertEqual(args.timeframe, "daily") - self.assertFalse(args.simulate) - self.assertFalse(args.verbose) +@mock.patch("logging.basicConfig") +def test_setup_logger_verbose(mock_basic_config): + AeonViewHelpers.setup_logger(verbose=True) + mock_basic_config.assert_called_once_with(level=logging.DEBUG, format="[%(levelname)s] %(message)s") -class TestAeonViewSimulation(TestCase): - @mock.patch("aeonview.AeonViewHelpers.mkdir_p") - @mock.patch("aeonview.AeonViewImages.download_image") - def test_image_simulation(self, mock_download_image, mock_mkdir_p): - args = mock.MagicMock() - args.simulate = True - args.date = "2025-04-10 12:30:45" - - url = f"{default_image_domain}.jpg" - project_path = Path("/tmp/test_project").resolve() - - avi = AeonViewImages(project_path, url, args) - with mock.patch("aeonview.logging.info") as mock_logging: - avi.get_current_image() - mock_mkdir_p.assert_not_called() - mock_download_image.assert_not_called() - mock_logging.assert_any_call( - f"Saving image to {project_path}/img/2025-04/10/12-30-45.jpg" - ) - - @mock.patch("aeonview.AeonViewHelpers.mkdir_p") - @mock.patch("subprocess.run") - def test_video_simulation(self, mock_subprocess_run, mock_mkdir_p): - args = mock.MagicMock() - args.simulate = True - args.fps = 10 - args.day = "01" - args.month = "01" - args.year = "2023" - project_path = Path("/tmp/test_project").resolve() - - avv = AeonViewVideos(project_path, args) - with mock.patch("aeonview.logging.info") as mock_logging: - avv.generate_daily_video() - mock_mkdir_p.assert_not_called() - mock_subprocess_run.assert_not_called() - mock_logging.assert_any_call( - f"Generating video from {project_path}/vid/2023-01/01" - ) - - -class TestSetupLogger(TestCase): - @mock.patch("logging.basicConfig") - def test_setup_logger_verbose(self, mock_basic_config): - AeonViewHelpers.setup_logger(verbose=True) - mock_basic_config.assert_called_once_with( - level=logging.DEBUG, format="[%(levelname)s] %(message)s" - ) - - @mock.patch("logging.basicConfig") - def test_setup_logger_non_verbose(self, mock_basic_config): - AeonViewHelpers.setup_logger(verbose=False) - mock_basic_config.assert_called_once_with( - level=logging.INFO, format="[%(levelname)s] %(message)s" - ) - mock_basic_config.reset_mock() +@mock.patch("logging.basicConfig") +def test_setup_logger_non_verbose(mock_basic_config): + AeonViewHelpers.setup_logger(verbose=False) + mock_basic_config.assert_called_once_with(level=logging.INFO, format="[%(levelname)s] %(message)s") diff --git a/requirements.txt b/requirements.txt index ca8dab3..6080c67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,6 @@ pre-commit>=3.5.0 # Testing pytest>=8.0.0 pytest-cov>=4.1.0 + # Linting & formatting ruff>=0.3.3