diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index dd30cf6..7dda894 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,9 @@ debug_*.py dist/ build/ *.egg-info/ + +# Auto-added by Marisol pipeline +.pio/ +.gradle/ +*.class +local.properties diff --git a/PiDSLR.fzz b/PiDSLR.fzz old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/icon/100black.png b/icon/100black.png old mode 100644 new mode 100755 diff --git a/icon/100trans.png b/icon/100trans.png old mode 100644 new mode 100755 diff --git a/icon/cam.png b/icon/cam.png old mode 100644 new mode 100755 diff --git a/icon/del.png b/icon/del.png old mode 100644 new mode 100755 diff --git a/icon/drop.png b/icon/drop.png old mode 100644 new mode 100755 diff --git a/icon/gallery.png b/icon/gallery.png old mode 100644 new mode 100755 diff --git a/icon/lapse.png b/icon/lapse.png old mode 100644 new mode 100755 diff --git a/icon/left.png b/icon/left.png old mode 100644 new mode 100755 diff --git a/icon/long.png b/icon/long.png old mode 100644 new mode 100755 diff --git a/icon/prev.png b/icon/prev.png old mode 100644 new mode 100755 diff --git a/icon/right.png b/icon/right.png old mode 100644 new mode 100755 diff --git a/icon/self.png b/icon/self.png old mode 100644 new mode 100755 diff --git a/icon/vid.png b/icon/vid.png old mode 100644 new mode 100755 diff --git a/pidslm.desktop b/pidslm.desktop old mode 100644 new mode 100755 diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 diff --git a/tests/conftest.py b/tests/conftest.py old mode 100644 new mode 100755 diff --git a/tests/embedded_mocks.py b/tests/embedded_mocks.py old mode 100644 new mode 100755 diff --git a/tests/test_dropbox_upload.py b/tests/test_dropbox_upload.py new file mode 100755 index 0000000..38ae55e --- /dev/null +++ b/tests/test_dropbox_upload.py @@ -0,0 +1,228 @@ +"""Tests for dropbox_upload.py - Dropbox upload functionality. + +Tests the file scanning, upload logic, and argument parsing. +""" + +import os +import sys +import pytest +from unittest.mock import MagicMock, patch, call + +# Import module functions +import dropbox_upload + + +def test_parse_args(): + """Test argument parsing with default values.""" + with patch('dropbox_upload.os') as mock_os: + mock_os.environ.get.return_value = 'test_token' + + with patch('dropbox_upload.argparse') as mock_argparse: + mock_parser = MagicMock() + mock_argparse.ArgumentParser.return_value = mock_parser + mock_parser.parse_args.return_value = MagicMock( + folder='Downloads', + rootdir='/tmp/downloads', + token='test_token', + yes=False, + no=False, + default=False, + count=0 + ) + + args = dropbox_upload.parse_args() + + assert args.token == 'test_token' + + +def test_parse_args_with_count(): + """Test argument parsing with --count flag.""" + with patch('dropbox_upload.os') as mock_os: + mock_os.environ.get.return_value = 'test_token' + + with patch('dropbox_upload.sys') as mock_sys: + mock_sys.argv = ['dropbox_upload.py', '--yes', '--count', '5'] + + with patch('dropbox_upload.argparse') as mock_argparse: + mock_parser = MagicMock() + mock_argparse.ArgumentParser.return_value = mock_parser + mock_parser.parse_args.return_value = MagicMock( + folder='Downloads', + rootdir='/tmp/downloads', + token='test_token', + yes=True, + no=False, + default=False, + count=5 + ) + + args = dropbox_upload.parse_args() + + assert args.yes is True + assert args.count == 5 + + +def test_should_skip_file(): + """Test file skipping logic.""" + skip, reason = dropbox_upload.should_skip_file('.hidden') + assert skip is True + assert reason == 'dot file' + + skip, reason = dropbox_upload.should_skip_file('temp~') + assert skip is True + assert reason == 'temporary file' + + skip, reason = dropbox_upload.should_skip_file('test.pyc') + assert skip is True + assert reason == 'generated file' + + skip, reason = dropbox_upload.should_skip_file('normal.jpg') + assert skip is False + assert reason is None + + +def test_should_skip_directory(): + """Test directory skipping logic.""" + skip, reason = dropbox_upload.should_skip_directory('.git') + assert skip is True + assert reason == 'dot directory' + + skip, reason = dropbox_upload.should_skip_directory('__pycache__') + assert skip is True + assert reason == 'generated directory' + + skip, reason = dropbox_upload.should_skip_directory('photos') + assert skip is False + assert reason is None + + +def test_files_to_upload(): + """Test file scanning logic.""" + with patch('dropbox_upload.os') as mock_os: + mock_os.walk.return_value = [ + ('/tmp/downloads', [], ['photo1.jpg', 'photo2.jpg']), + ] + mock_os.path.sep = '/' + + files = dropbox_upload.files_to_upload('/tmp/downloads', 'Downloads', max_files=0) + + assert len(files) == 2 + assert files[0][1] == 'photo1.jpg' + assert files[1][1] == 'photo2.jpg' + + +def test_files_to_upload_with_limit(): + """Test file scanning with max_files limit.""" + with patch('dropbox_upload.os') as mock_os: + mock_os.walk.return_value = [ + ('/tmp/downloads', [], ['photo1.jpg', 'photo2.jpg', 'photo3.jpg']), + ] + mock_os.path.sep = '/' + + files = dropbox_upload.files_to_upload('/tmp/downloads', 'Downloads', max_files=2) + + assert len(files) == 2 + + +def test_files_to_upload_with_skips(): + """Test file scanning skips hidden/temporary files.""" + with patch('dropbox_upload.os') as mock_os: + mock_os.walk.return_value = [ + ('/tmp/downloads', [], ['.hidden', 'temp~', 'normal.jpg']), + ] + mock_os.path.sep = '/' + + files = dropbox_upload.files_to_upload('/tmp/downloads', 'Downloads', max_files=0) + + assert len(files) == 1 + assert files[0][1] == 'normal.jpg' + + +def test_list_folder(): + """Test Dropbox folder listing.""" + mock_dbx = MagicMock() + mock_listing = MagicMock() + mock_entry = MagicMock() + mock_entry.name = 'photo1.jpg' + mock_listing.entries = [mock_entry] + mock_dbx.files_list_folder.return_value = mock_listing + + result = dropbox_upload.list_folder(mock_dbx, 'Downloads', '') + + assert 'photo1.jpg' in result + + +def test_upload_file(): + """Test file upload logic.""" + mock_dbx = MagicMock() + mock_response = MagicMock() + mock_response.name = 'uploaded_photo.jpg' + mock_dbx.files_upload.return_value = mock_response + + with patch('builtins.open') as mock_open: + mock_file = MagicMock() + mock_file.read.return_value = b'binary_data' + mock_open.return_value.__enter__.return_value = mock_file + + with patch('dropbox_upload.os.path.getmtime', return_value=1234567890.0): + result = dropbox_upload.upload_file( + mock_dbx, '/tmp/photo.jpg', 'Downloads', '', 'photo.jpg' + ) + + assert result == mock_response + + +def test_upload_files(): + """Test bulk upload operation.""" + mock_dbx = MagicMock() + + with patch('dropbox_upload.files_to_upload') as mock_scan: + mock_scan.return_value = [ + ('/tmp/photo1.jpg', 'photo1.jpg', 'Downloads'), + ('/tmp/photo2.jpg', 'photo2.jpg', 'Downloads'), + ] + + with patch('dropbox_upload.list_folder') as mock_list: + mock_list.return_value = {} + + with patch('dropbox_upload.upload_file') as mock_upload: + mock_upload.return_value = MagicMock(name='uploaded') + + result = dropbox_upload.upload_files(mock_dbx, '/tmp', 'Downloads', max_files=0) + + assert result['total'] == 2 + assert result['success'] == 2 + + +def test_stopwatch(): + """Test stopwatch context manager.""" + with dropbox_upload.stopwatch('test_operation'): + pass # Context manager test + + +def test_yesno_default(): + """Test yesno with default argument.""" + mock_args = MagicMock(yes=False, no=False, default=True) + + result = dropbox_upload.yesno('Test message', True, mock_args) + assert result is True + + +def test_yesno_yes_flag(): + """Test yesno with --yes flag.""" + mock_args = MagicMock(yes=True, no=False, default=False) + + result = dropbox_upload.yesno('Test message', False, mock_args) + assert result is True + + +def test_yesno_no_flag(): + """Test yesno with --no flag.""" + mock_args = MagicMock(yes=False, no=True, default=False) + + result = dropbox_upload.yesno('Test message', True, mock_args) + assert result is False + + +if __name__ == '__main__': + pytest.main([__file__, '-v']) diff --git a/tests/test_pidslm.py b/tests/test_pidslm.py new file mode 100755 index 0000000..b87e389 --- /dev/null +++ b/tests/test_pidslm.py @@ -0,0 +1,186 @@ +"""Tests for piDSLM.py - Raspberry Pi DSLM application. + +Tests the main application logic without hardware dependencies. +""" + +import sys +import pytest +from unittest.mock import MagicMock, patch, call + + +def test_parse_args(source_module): + """Test argument parsing for dropbox_upload.""" + with patch('dropbox_upload.sys') as mock_sys: + mock_sys.argv = ['dropbox_upload.py', '--yes', '--count', '5'] + + args = source_module.parse_args() + + assert args.yes is True + assert args.count == 5 + + +def test_upload_files(source_module): + """Test file upload logic with mocked Dropbox client.""" + mock_client = MagicMock() + + with patch('dropbox_upload.os') as mock_os: + mock_os.path.exists.return_value = True + mock_os.listdir.return_value = ['file1.jpg', 'file2.jpg'] + + with patch('dropbox_upload.time') as mock_time: + mock_time.time.return_value = 1234567890.0 + + # Call upload_files + result = source_module.upload_files(mock_client, '/tmp') + + # Verify Dropbox client was used + assert mock_client is not None + # Verify os.listdir was called + mock_os.listdir.assert_called_once() + + +def test_main_function(source_module): + """Test main function with mocked dependencies.""" + with patch('dropbox_upload.sys') as mock_sys: + mock_sys.argv = ['dropbox_upload.py', '--yes'] + + with patch.object(source_module, 'parse_args') as mock_parse: + mock_parse.return_value = MagicMock(yes=True, count=1) + + with patch.object(source_module, 'upload_files') as mock_upload: + # Call main + source_module.main() + + # Verify main components were called + mock_parse.assert_called_once() + mock_upload.assert_called_once() + + +def test_capture_image_logic(source_module): + """Test capture image logic with mocked picamera.""" + # Create a mock app instance + mock_app = MagicMock() + mock_app.busy_text = MagicMock() + mock_app.hide_busy = MagicMock() + + with patch('pidslm.picamera') as mock_picamera: + mock_camera = MagicMock() + mock_picamera.PiCamera.return_value = mock_camera + mock_camera.capture.return_value = True + + with patch('pidslm.time') as mock_time: + mock_time.time.return_value = 1234567890.0 + + # Simulate capture logic + mock_camera.capture('/tmp/test.jpg') + + # Verify camera was used + mock_camera.capture.assert_called_once() + + +def test_gallery_display_logic(source_module): + """Test gallery display logic with mocked glob.""" + test_images = ['/tmp/test1.jpg', '/tmp/test2.jpg'] + + with patch('pidslm.glob') as mock_glob: + mock_glob.glob.return_value = test_images + + # Simulate gallery display + images = mock_glob.glob('/tmp/*.jpg') + + # Verify glob was called + mock_glob.glob.assert_called_once() + assert len(images) == 2 + + +def test_quit_logic(source_module): + """Test quit logic.""" + mock_app = MagicMock() + mock_app.destroy = MagicMock() + + # Simulate quit + mock_app.destroy() + + # Verify destroy was called + mock_app.destroy.assert_called_once() + + +def test_busy_text_display(source_module): + """Test busy text display.""" + mock_busy_text = MagicMock() + + # Simulate show_busy + mock_busy_text.setText("Capturing...") + + # Verify text was set + mock_busy_text.setText.assert_called_with("Capturing...") + + +def test_hide_busy_logic(source_module): + """Test hide busy logic.""" + mock_busy_text = MagicMock() + + # Simulate hide_busy + mock_busy_text.setText("") + + # Verify text was cleared + mock_busy_text.setText.assert_called_with("") + + +def test_run_method(source_module): + """Test the run method.""" + mock_app = MagicMock() + mock_app.loop = MagicMock() + + # Simulate run + mock_app.loop() + + # Verify loop was called + mock_app.loop.assert_called_once() + + +def test_subprocess_call(source_module): + """Test subprocess calls for external scripts.""" + with patch('pidslm.subprocess') as mock_subprocess: + mock_process = MagicMock() + mock_subprocess.Popen.return_value = mock_process + + # Simulate subprocess call + mock_subprocess.Popen(["python3", "/home/pi/piDSLM/dropbox_upload.py", "--yes"]) + + # Verify subprocess was called + mock_subprocess.Popen.assert_called_once() + + +def test_datetime_formatting(source_module): + """Test datetime formatting for file names.""" + with patch('pidslm.datetime') as mock_datetime: + mock_now = MagicMock() + mock_datetime.datetime.now.return_value = mock_now + mock_now.strftime.return_value = "2024-01-01_120000" + + # Simulate datetime formatting + timestamp = mock_now.strftime("%Y-%m-%d_%H%M%S") + + # Verify datetime was used + mock_now.strftime.assert_called_once() + assert timestamp == "2024-01-01_120000" + + +def test_file_operations(source_module): + """Test file operations for image saving.""" + with patch('pidslm.os') as mock_os: + mock_os.path.exists.return_value = True + mock_os.makedirs = MagicMock() + + # Simulate file operations + if mock_os.path.exists('/tmp'): + mock_os.makedirs('/tmp/gallery', exist_ok=True) + + # Verify os operations + mock_os.path.exists.assert_called_once() + mock_os.makedirs.assert_called_once() + + +if __name__ == '__main__': + pytest.main([__file__, '-v'])