def test_listing_albums(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with vcr.use_cassette("tests/vcr_cassettes/listing_albums.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--list-albums", "--no-progress-bar", ], ) print_result_exception(result) albums = result.output.splitlines() self.assertIn("All Photos", albums) self.assertIn("WhatsApp", albums) self.assertIn("Time-lapse", albums) self.assertIn("Recently Deleted", albums) self.assertIn("Favorites", albums) assert result.exit_code == 0
def test_handle_session_error_during_photo_iteration(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" def mock_raise_response_error(offset): raise PyiCloudAPIResponseError("Invalid global session", 100) with mock.patch("time.sleep") as sleep_mock: with mock.patch.object(PhotoAlbum, "photos_request") as pa_photos_request: pa_photos_request.side_effect = mock_raise_response_error # Let the initial authenticate() call succeed, # but do nothing on the second try. orig_authenticate = PyiCloudService.authenticate def mocked_authenticate(self): if not hasattr(self, "already_authenticated"): orig_authenticate(self) setattr(self, "already_authenticated", True) with mock.patch.object(PyiCloudService, "authenticate", new=mocked_authenticate): runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--skip-videos", "--skip-live-photos", "--no-progress-bar", "-d", "tests/fixtures/Photos", ], ) print_result_exception(result) # Error msg should be repeated 5 times assert (self._caplog.text.count( "Session error, re-authenticating...") == 5) self.assertIn( "INFO iCloud re-authentication failed! Please try again later.", self._caplog.text, ) # Make sure we only call sleep 4 times (skip the first retry) self.assertEquals(sleep_mock.call_count, 4) assert result.exit_code == -1
def test_listing_recent_photos_with_missing_downloadURL(self): base_dir = os.path.normpath("tests/fixtures/Photos") if os.path.exists(base_dir): shutil.rmtree(base_dir) os.makedirs(base_dir) # Note - This test uses the same cassette as test_download_photos.py with vcr.use_cassette( "tests/vcr_cassettes/listing_photos_missing_downloadUrl.yml"): with mock.patch("icloudpd.base.open", create=True) as mock_open: with mock.patch.object(json, "dump") as mock_json: # Pass fixed client ID via environment variable os.environ[ "CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--only-print-filenames", "--no-progress-bar", "--threads-num", 1, "-d", base_dir, ], ) print_result_exception(result) self.assertEqual.__self__.maxDiff = None self.assertEqual( """\ KeyError: 'downloadURL' attribute was not found in the photo fields! icloudpd has saved the photo record to: ./icloudpd-photo-error.json Please create a Gist with the contents of this file: https://gist.github.com Then create an issue on GitHub: https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues Include a link to the Gist in your issue, so that we can see what went wrong. """, result.output) mock_open.assert_called_once_with( 'icloudpd-photo-error.json', 'w') mock_json.assert_called_once() # Check a few keys in the dict first_arg = mock_json.call_args_list[0][0][0] self.assertEqual(first_arg['master_record']['recordName'], 'AY6c+BsE0jjaXx9tmVGJM1D2VcEO') self.assertEqual( first_arg['master_record']['fields'] ['resVidSmallHeight']['value'], 581) self.assertEqual(first_arg['asset_record']['recordName'], 'F2A23C38-0020-42FE-A273-2923ADE3CAED') self.assertEqual( first_arg['asset_record']['fields']['assetDate'] ['value'], 1533021744816) assert result.exit_code == 0
def test_missing_size(self): base_dir = "tests/fixtures/Photos" if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with mock.patch.object(PhotoAsset, "download") as pa_download: pa_download.return_value = False with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "3", "--no-progress-bar", base_dir, ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos...", self._caplog.text ) self.assertIn( "INFO Downloading 3 original photos and videos to tests/fixtures/Photos/ ...", self._caplog.text, ) # These error messages should not be repeated more than once assert ( self._caplog.text.count( "ERROR Could not find URL to download IMG_7409.JPG for size original!" ) == 1 ) assert ( self._caplog.text.count( "ERROR Could not find URL to download IMG_7408.JPG for size original!" ) == 1 ) assert ( self._caplog.text.count( "ERROR Could not find URL to download IMG_7407.JPG for size original!" ) == 1 ) self.assertIn( "INFO All photos have been downloaded!", self._caplog.text ) assert result.exit_code == 0
def test_listing_recent_photos(self): base_dir = os.path.normpath(f"tests/fixtures/Photos/{inspect.stack()[0][3]}") if os.path.exists(base_dir): shutil.rmtree(base_dir) os.makedirs(base_dir) # Note - This test uses the same cassette as test_download_photos.py with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable runner = CliRunner(env={ "CLIENT_ID": "DE309E26-942E-11E8-92F5-14109FE0B321" }) result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "5", "--only-print-filenames", "--no-progress-bar", "--threads-num", 1, "-d", base_dir, ], ) print_result_exception(result) filenames = result.output.splitlines() self.assertEqual(len(filenames), 8) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/31/IMG_7409.JPG")), filenames[0] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/31/IMG_7409.MOV")), filenames[1] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/30/IMG_7408.JPG")), filenames[2] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/30/IMG_7408.MOV")), filenames[3] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/30/IMG_7407.JPG")), filenames[4] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/30/IMG_7407.MOV")), filenames[5] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/30/IMG_7405.MOV")), filenames[6] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/30/IMG_7404.MOV")), filenames[7] ) assert result.exit_code == 0
def test_skip_existing_live_photo_print_filenames(self): base_dir = os.path.normpath("tests/fixtures/Photos") if os.path.exists(base_dir): shutil.rmtree(base_dir) os.makedirs(base_dir) # simulate that some of the expected files are there with corret sizes os.makedirs(os.path.join(base_dir, "2020/11/04")) # one photo and one movie are already there and should be skipped # Create dummies with the correct size with open(os.path.join(base_dir, "2020/11/04/IMG_0516.HEIC"), "a") as f: f.truncate(1651485) with open(os.path.join(base_dir, "2020/11/04/IMG_0514_HEVC.MOV"), "a") as f: f.truncate(3951774) with vcr.use_cassette("tests/vcr_cassettes/download_live_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "3", "--no-progress-bar", "--threads-num", 1, "--only-print-filenames", "-d", base_dir, ], ) print_result_exception(result) filenames = result.output.splitlines() print (filenames) self.assertEqual( os.path.join(base_dir, os.path.normpath("2020/11/04/IMG_0514.HEIC")), filenames[0] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2020/11/04/IMG_0512.HEIC")), filenames[1] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2020/11/04/IMG_0512_HEVC.MOV")), filenames[2] ) # Double check that a mocked file does not get listed again. Its already there! assert "2020/11/04/IMG_0514_HEVC.MOV" not in filenames assert result.exit_code == 0
def test_listing_recent_photos_with_missing_filenameEnc(self): base_dir = os.path.normpath(f"tests/fixtures/Photos/{inspect.stack()[0][3]}") if os.path.exists(base_dir): shutil.rmtree(base_dir) os.makedirs(base_dir) # Note - This test uses the same cassette as test_download_photos.py with vcr.use_cassette("tests/vcr_cassettes/listing_photos_missing_filenameEnc.yml"): with mock.patch("icloudpd.base.open", create=True) as mock_open: with mock.patch.object(json, "dump") as mock_json: # Pass fixed client ID via environment variable runner = CliRunner(env={ "CLIENT_ID": "DE309E26-942E-11E8-92F5-14109FE0B321" }) result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "5", "--only-print-filenames", "--no-progress-bar", "--threads-num", 1, "-d", base_dir, ], ) print_result_exception(result) self.assertEqual.__self__.maxDiff = None filenames = result.output.splitlines() # self.assertEqual(len(filenames), 5) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/31/AY6c_BsE0jja.JPG")), filenames[0] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/31/AY6c_BsE0jja.MOV")), filenames[1] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/30/IMG_7408.JPG")), filenames[2] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/30/IMG_7408.MOV")), filenames[3] ) self.assertEqual( os.path.join(base_dir, os.path.normpath("2018/07/30/AZ_wAGT9P6jh.JPG")), filenames[4] ) assert result.exit_code == 0
def test_handle_connection_error(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" def mock_raise_response_error(arg): raise ConnectionError("Connection Error") with mock.patch.object(PhotoAsset, "download") as pa_download: pa_download.side_effect = mock_raise_response_error # Let the initial authenticate() call succeed, # but do nothing on the second try. orig_authenticate = PyiCloudService.authenticate def mocked_authenticate(self): if not hasattr(self, "already_authenticated"): orig_authenticate(self) setattr(self, "already_authenticated", True) with mock.patch("icloudpd.constants.WAIT_SECONDS", 0): with mock.patch.object( PyiCloudService, "authenticate", new=mocked_authenticate ): runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--skip-videos", "--skip-live-photos", "--no-progress-bar", "tests/fixtures/Photos", ], ) print_result_exception(result) # Error msg should be repeated 5 times assert ( self._caplog.text.count( "Error downloading IMG_7409.JPG, retrying after 0 seconds..." ) == 5 ) self.assertIn( "INFO Could not download IMG_7409.JPG! Please try again later.", self._caplog.text, ) assert result.exit_code == 0
def test_invalid_creation_date(self): base_dir = "tests/fixtures/Photos" if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with mock.patch.object(PhotoAsset, "created", new_callable=mock.PropertyMock) as dt_mock: # Can't mock `astimezone` because it's a readonly property, so have to # create a new class that inherits from datetime.datetime class NewDateTime(datetime.datetime): def astimezone(self, tz=None): raise ValueError('Invalid date') dt_mock.return_value = NewDateTime(2018, 1, 1, 0, 0, 0) with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ[ "CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--skip-live-photos", "--no-progress-bar", "-d", base_dir, ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos from album All Photos...", self._caplog.text, ) self.assertIn( "INFO Downloading the first original photo or video to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "ERROR Could not convert photo created date to local timezone (2018-01-01 00:00:00)", self._caplog.text, ) self.assertIn( "INFO Downloading tests/fixtures/Photos/2018/01/01/IMG_7409.JPG", self._caplog.text, ) self.assertIn("INFO All photos have been downloaded!", self._caplog.text) assert result.exit_code == 0
def test_size_fallback_to_original(self): base_dir = "tests/fixtures/Photos" if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with mock.patch("icloudpd.download.download_media") as dp_patched: dp_patched.return_value = True with mock.patch.object(PhotoAsset, "versions") as pa: pa.return_value = ["original", "medium"] with vcr.use_cassette( "tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ[ "CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--size", "thumb", "--no-progress-bar", "-d", base_dir, ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos from album All Photos...", self._caplog.text, ) self.assertIn( "INFO Downloading the first thumb photo or video to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO Downloading tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) self.assertIn("INFO All photos have been downloaded!", self._caplog.text) dp_patched.assert_called_once_with( ANY, ANY, "tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", "original", ) assert result.exit_code == 0
def test_folder_structure_none(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") # Note - This test uses the same cassette as test_download_photos.py with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "5", "--only-print-filenames", "--folder-structure=none", "--no-progress-bar", "-d", "tests/fixtures/Photos", ], ) print_result_exception(result) filenames = result.output.splitlines() self.assertEqual(len(filenames), 8) self.assertEqual( normpath("tests/fixtures/Photos/IMG_7409.JPG"), filenames[0] ) self.assertEqual( normpath("tests/fixtures/Photos/IMG_7409.MOV"), filenames[1] ) self.assertEqual( normpath("tests/fixtures/Photos/IMG_7408.JPG"), filenames[2] ) self.assertEqual( normpath("tests/fixtures/Photos/IMG_7408.MOV"), filenames[3] ) self.assertEqual( normpath("tests/fixtures/Photos/IMG_7407.JPG"), filenames[4] ) self.assertEqual( normpath("tests/fixtures/Photos/IMG_7407.MOV"), filenames[5] ) self.assertEqual( normpath("tests/fixtures/Photos/IMG_7405.MOV"), filenames[6] ) self.assertEqual( normpath("tests/fixtures/Photos/IMG_7404.MOV"), filenames[7] ) assert result.exit_code == 0
def test_size_fallback_to_original(self): base_dir = "tests/fixtures/Photos" if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with mock.patch("icloudpd.download.download_media") as dp_patched: dp_patched.return_value = True with mock.patch.object(PhotoAsset, "versions") as pa: pa.return_value = ["original", "medium"] with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--size", "thumb", "--no-progress-bar", base_dir, ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos...", self._caplog.text, ) self.assertIn( "INFO Downloading the first thumb photo or video to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO Downloading tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) self.assertIn( "INFO All photos have been downloaded!", self._caplog.text ) dp_patched.assert_called_once_with( ANY, ANY, "tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", "original", ) assert result.exit_code == 0
def test_listing_recent_photos(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") # Note - This test uses the same cassette as test_download_photos.py with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "5", "--only-print-filenames", "--no-progress-bar", "tests/fixtures/Photos", ], ) print_result_exception(result) filenames = result.output.splitlines() self.assertEqual(len(filenames), 8) self.assertEqual( "tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", filenames[0] ) self.assertEqual( "tests/fixtures/Photos/2018/07/31/IMG_7409.MOV", filenames[1] ) self.assertEqual( "tests/fixtures/Photos/2018/07/30/IMG_7408.JPG", filenames[2] ) self.assertEqual( "tests/fixtures/Photos/2018/07/30/IMG_7408.MOV", filenames[3] ) self.assertEqual( "tests/fixtures/Photos/2018/07/30/IMG_7407.JPG", filenames[4] ) self.assertEqual( "tests/fixtures/Photos/2018/07/30/IMG_7407.MOV", filenames[5] ) self.assertEqual( "tests/fixtures/Photos/2018/07/30/IMG_7405.MOV", filenames[6] ) self.assertEqual( "tests/fixtures/Photos/2018/07/30/IMG_7404.MOV", filenames[7] ) assert result.exit_code == 0
def test_invalid_creation_date(self): base_dir = "tests/fixtures/Photos" if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with mock.patch.object(PhotoAsset, "created", new_callable=mock.PropertyMock) as dt_mock: # Can't mock `astimezone` because it's a readonly property, so have to # create a new class that inherits from datetime.datetime class NewDateTime(datetime.datetime): def astimezone(self, tz=None): raise ValueError('Invalid date') dt_mock.return_value = NewDateTime(2018,1,1,0,0,0) with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--skip-live-photos", "--no-progress-bar", base_dir, ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos...", self._caplog.text, ) self.assertIn( "INFO Downloading the first original photo or video to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "ERROR Could not convert photo created date to local timezone (2018-01-01 00:00:00)", self._caplog.text, ) self.assertIn( "INFO Downloading tests/fixtures/Photos/2018/01/01/IMG_7409.JPG", self._caplog.text, ) self.assertIn( "INFO All photos have been downloaded!", self._caplog.text ) assert result.exit_code == 0
def test_download_photos_and_exif_exceptions(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with mock.patch.object(piexif, "load") as piexif_patched: piexif_patched.side_effect = InvalidImageDataError with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ[ "CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--skip-videos", "--skip-live-photos", "--set-exif-datetime", "--no-progress-bar", "-d", "tests/fixtures/Photos", ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos from album All Photos...", self._caplog.text) self.assertIn( "INFO Downloading the first original photo to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO Downloading tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) self.assertIn( "DEBUG Error fetching EXIF data for tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) self.assertIn( "DEBUG Error setting EXIF data for tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) self.assertIn("INFO All photos have been downloaded!", self._caplog.text) assert result.exit_code == 0
def test_listing_recent_photos_with_missing_downloadURL(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") # Note - This test uses the same cassette as test_download_photos.py with vcr.use_cassette("tests/vcr_cassettes/listing_photos_missing_downloadUrl.yml"): with mock.patch("icloudpd.base.open", create=True) as mock_open: with mock.patch.object(json, "dump") as mock_json: # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--only-print-filenames", "--no-progress-bar", "tests/fixtures/Photos", ], ) print_result_exception(result) self.assertEqual.__self__.maxDiff = None self.assertEqual("""\ KeyError: 'downloadURL' attribute was not found in the photo fields! icloudpd has saved the photo record to: ./icloudpd-photo-error.json Please create a Gist with the contents of this file: https://gist.github.com Then create an issue on GitHub: https://github.com/ndbroadbent/icloud_photos_downloader/issues Include a link to the Gist in your issue, so that we can see what went wrong. """ , result.output) mock_open.assert_called_once_with('icloudpd-photo-error.json', 'w') mock_json.assert_called_once() # Check a few keys in the dict first_arg = mock_json.call_args_list[0][0][0] self.assertEqual( first_arg['master_record']['recordName'], 'AY6c+BsE0jjaXx9tmVGJM1D2VcEO') self.assertEqual( first_arg['master_record']['fields']['resVidSmallHeight']['value'], 581) self.assertEqual( first_arg['asset_record']['recordName'], 'F2A23C38-0020-42FE-A273-2923ADE3CAED') self.assertEqual( first_arg['asset_record']['fields']['assetDate']['value'], 1533021744816) assert result.exit_code == 0
def test_skip_existing_downloads_sqlite(self, mock_sqlite): # all media inqueries return as already processed mock_sqlite().processed.return_value = True if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos/2018/07/31/") open("tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", "a").close() open("tests/fixtures/Photos/2018/07/31/IMG_7409.MOV", "a").close() with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", # '--skip-videos', # "--skip-live-photos", "--no-progress-bar", "-d", "tests/fixtures/Photos", "--state-store", "sqlite", "--state-path", "tests/fixtures/test.sqlite3" ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos from album All Photos...", self._caplog.text) self.assertIn( "INFO Downloading the first original photo or video to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO tests/fixtures/Photos/2018/07/31/IMG_7409.JPG already processed.", self._caplog.text, ) self.assertIn( "INFO tests/fixtures/Photos/2018/07/31/IMG_7409.MOV already processed.", self._caplog.text, ) self.assertIn("INFO All photos have been downloaded!", self._caplog.text) assert result.exit_code == 0
def test_unknown_item_type(self): base_dir = "tests/fixtures/Photos" if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with mock.patch("icloudpd.download.download_media") as dp_patched: dp_patched.return_value = True with mock.patch.object(PhotoAsset, "item_type", new_callable=mock.PropertyMock) as it_mock: it_mock.return_value = 'unknown' with vcr.use_cassette( "tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ[ "CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--no-progress-bar", "-d", base_dir, ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos from album All Photos...", self._caplog.text, ) self.assertIn( "INFO Downloading the first original photo or video to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO Skipping IMG_7409.JPG, only downloading photos and videos. (Item type was: unknown)", self._caplog.text, ) self.assertIn("INFO All photos have been downloaded!", self._caplog.text) dp_patched.assert_not_called assert result.exit_code == 0
def test_download_photos_and_exif_exceptions(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with mock.patch.object(piexif, "load") as piexif_patched: piexif_patched.side_effect = InvalidImageDataError with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--skip-videos", "--skip-live-photos", "--set-exif-datetime", "--no-progress-bar", "tests/fixtures/Photos", ], ) print_result_exception(result) self.assertIn("DEBUG Looking up all photos...", self._caplog.text) self.assertIn( "INFO Downloading the first original photo to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO Downloading tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) self.assertIn( "DEBUG Error fetching EXIF data for tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) self.assertIn( "DEBUG Error setting EXIF data for tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) self.assertIn( "INFO All photos have been downloaded!", self._caplog.text ) assert result.exit_code == 0
def test_listing_recent_photos_with_missing_filenameEnc(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") # Note - This test uses the same cassette as test_download_photos.py with vcr.use_cassette("tests/vcr_cassettes/listing_photos_missing_filenameEnc.yml"): with mock.patch("icloudpd.base.open", create=True) as mock_open: with mock.patch.object(json, "dump") as mock_json: # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "5", "--only-print-filenames", "--no-progress-bar", "tests/fixtures/Photos", ], ) print_result_exception(result) self.assertEqual.__self__.maxDiff = None filenames = result.output.splitlines() # self.assertEqual(len(filenames), 5) self.assertEqual( "tests/fixtures/Photos/2018/07/31/AY6c_BsE0jja.JPG", filenames[0] ) self.assertEqual( "tests/fixtures/Photos/2018/07/31/AY6c_BsE0jja.MOV", filenames[1] ) self.assertEqual( "tests/fixtures/Photos/2018/07/30/IMG_7408.JPG", filenames[2] ) self.assertEqual( "tests/fixtures/Photos/2018/07/30/IMG_7408.MOV", filenames[3] ) self.assertEqual( "tests/fixtures/Photos/2018/07/30/AZ_wAGT9P6jh.JPG", filenames[4] ) assert result.exit_code == 0
def test_unknown_item_type(self): base_dir = "tests/fixtures/Photos" if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with mock.patch("icloudpd.download.download_media") as dp_patched: dp_patched.return_value = True with mock.patch.object(PhotoAsset, "item_type", new_callable=mock.PropertyMock) as it_mock: it_mock.return_value = 'unknown' with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--no-progress-bar", base_dir, ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos...", self._caplog.text, ) self.assertIn( "INFO Downloading the first original photo or video to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO Skipping IMG_7409.JPG, only downloading photos and videos. (Item type was: unknown)", self._caplog.text, ) self.assertIn( "INFO All photos have been downloaded!", self._caplog.text ) dp_patched.assert_not_called assert result.exit_code == 0
def test_skip_existing_downloads(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos/2018/07/31/") open("tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", "a").close() open("tests/fixtures/Photos/2018/07/31/IMG_7409.MOV", "a").close() with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", # '--skip-videos', # "--skip-live-photos", "--no-progress-bar", "tests/fixtures/Photos", ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos...", self._caplog.text ) self.assertIn( "INFO Downloading the first original photo or video to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO tests/fixtures/Photos/2018/07/31/IMG_7409.JPG already exists.", self._caplog.text, ) self.assertIn( "INFO tests/fixtures/Photos/2018/07/31/IMG_7409.MOV already exists.", self._caplog.text, ) self.assertIn( "INFO All photos have been downloaded!", self._caplog.text ) assert result.exit_code == 0
def test_handle_io_error(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" with mock.patch("icloudpd.download.open", create=True) as m: # Raise IOError when we try to write to the destination file m.side_effect = IOError runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--skip-videos", "--skip-live-photos", "--no-progress-bar", "-d" "tests/fixtures/Photos", ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos from album All Photos...", self._caplog.text) self.assertIn( "INFO Downloading the first original photo to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "ERROR IOError while writing file to " "tests/fixtures/Photos/2018/07/31/IMG_7409.JPG! " "You might have run out of disk space, or the file might " "be too large for your OS. Skipping this file...", self._caplog.text, ) assert result.exit_code == 0
def test_skip_existing_downloads_for_live_photos(self): base_dir = os.path.normpath( f"tests/fixtures/Photos/{inspect.stack()[0][3]}") if os.path.exists(base_dir): shutil.rmtree(base_dir) os.makedirs(base_dir) with vcr.use_cassette("tests/vcr_cassettes/download_live_photos.yml"): # Pass fixed client ID via environment variable runner = CliRunner( env={"CLIENT_ID": "DE309E26-942E-11E8-92F5-14109FE0B321"}) result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "3", "--no-progress-bar", "--threads-num", 1, "-d", base_dir, ], ) print_result_exception(result) self.assertIn( f"INFO Downloading {os.path.join(base_dir, os.path.normpath('2020/11/04/IMG_0514_HEVC.MOV'))}", self._caplog.text, ) self.assertIn( f"INFO Downloading {os.path.join(base_dir, os.path.normpath('2020/11/04/IMG_0514.HEIC'))}", self._caplog.text, ) self.assertIn( f"INFO Downloading {os.path.join(base_dir, os.path.normpath('2020/11/04/IMG_0516.HEIC'))}", self._caplog.text, ) self.assertIn("INFO All photos have been downloaded!", self._caplog.text) assert result.exit_code == 0
def test_handle_io_error(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" with mock.patch("icloudpd.download.open", create=True) as m: # Raise IOError when we try to write to the destination file m.side_effect = IOError runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--skip-videos", "--skip-live-photos", "--no-progress-bar", "tests/fixtures/Photos", ], ) print_result_exception(result) self.assertIn("DEBUG Looking up all photos...", self._caplog.text) self.assertIn( "INFO Downloading the first original photo to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "ERROR IOError while writing file to " "tests/fixtures/Photos/2018/07/31/IMG_7409.JPG! " "You might have run out of disk space, or the file might " "be too large for your OS. Skipping this file...", self._caplog.text, ) assert result.exit_code == 0
def test_listing_photos_does_not_create_folders(self): base_dir = os.path.normpath("tests/fixtures/Photos") if os.path.exists(base_dir): shutil.rmtree(base_dir) os.makedirs(base_dir) # make sure the directory does not exist yet. # Should only be created after download, not after just --print-filenames self.assertFalse( os.path.exists( os.path.join(base_dir, os.path.normpath("2018/07/31")))) # Note - This test uses the same cassette as test_download_photos.py with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "5", "--only-print-filenames", "--no-progress-bar", "--threads-num", 1, "-d", base_dir, ], ) print_result_exception(result) # make sure the directory still does not exist. # Should only be created after download, not after just --print-filenames self.assertFalse( os.path.exists( os.path.join(base_dir, os.path.normpath("2018/07/31")))) assert result.exit_code == 0
def test_skip_existing_live_photodownloads(self): base_dir = os.path.normpath( f"tests/fixtures/Photos/{inspect.stack()[0][3]}") if os.path.exists(base_dir): shutil.rmtree(base_dir) os.makedirs(base_dir) # simulate that some of the expected files are there with corret sizes os.makedirs(os.path.join(base_dir, "2020/11/04")) # one photo and one movie are already there and should be skipped # Create dummies with the correct size with open(os.path.join(base_dir, "2020/11/04/IMG_0516.HEIC"), "a") as f: f.truncate(1651485) with open(os.path.join(base_dir, "2020/11/04/IMG_0514_HEVC.MOV"), "a") as f: f.truncate(3951774) with vcr.use_cassette("tests/vcr_cassettes/download_live_photos.yml"): # Pass fixed client ID via environment variable runner = CliRunner( env={"CLIENT_ID": "DE309E26-942E-11E8-92F5-14109FE0B321"}) result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "3", "--no-progress-bar", "--threads-num", 1, "-d", base_dir, ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos from album All Photos...", self._caplog.text) self.assertIn( f"INFO Downloading 3 original photos and videos to {base_dir} ...", self._caplog.text, ) self.assertIn( f"INFO Downloading {os.path.join(base_dir, os.path.normpath('2020/11/04/IMG_0514.HEIC'))}", self._caplog.text, ) self.assertIn( f"INFO {os.path.join(base_dir, os.path.normpath('2020/11/04/IMG_0514_HEVC.MOV'))} already exists.", self._caplog.text, ) self.assertIn( f"INFO Downloading {os.path.join(base_dir, os.path.normpath('2020/11/04/IMG_0514.HEIC'))}", self._caplog.text, ) self.assertIn( f"INFO {os.path.join(base_dir, os.path.normpath('2020/11/04/IMG_0516.HEIC'))} already exists.", self._caplog.text, ) self.assertIn("INFO All photos have been downloaded!", self._caplog.text) assert result.exit_code == 0
def test_download_photos_and_set_exif(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos/2018/07/30/") open("tests/fixtures/Photos/2018/07/30/IMG_7408.JPG", "a").close() open("tests/fixtures/Photos/2018/07/30/IMG_7407.JPG", "a").close() # Download the first photo, but mock the video download orig_download = PhotoAsset.download def mocked_download(self, size): if not hasattr(PhotoAsset, "already_downloaded"): response = orig_download(self, size) setattr(PhotoAsset, "already_downloaded", True) return response return mock.MagicMock() with mock.patch.object(PhotoAsset, "download", new=mocked_download): with mock.patch( "icloudpd.exif_datetime.get_photo_exif" ) as get_exif_patched: get_exif_patched.return_value = False with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "4", "--set-exif-datetime", # '--skip-videos', # "--skip-live-photos", "--no-progress-bar", "tests/fixtures/Photos", ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos...", self._caplog.text, ) self.assertIn( "INFO Downloading 4 original photos and videos to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO Downloading tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) # YYYY:MM:DD is the correct format. self.assertIn( "DEBUG Setting EXIF timestamp for tests/fixtures/Photos/2018/07/31/IMG_7409.JPG: 2018:07:31", self._caplog.text, ) self.assertIn( "INFO All photos have been downloaded!", self._caplog.text ) assert result.exit_code == 0
def test_until_found(self): base_dir = "tests/fixtures/Photos" if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos/2018/07/30/") os.makedirs("tests/fixtures/Photos/2018/07/31/") files_to_download = [] files_to_skip = [] files_to_download.append(("2018/07/31/IMG_7409.JPG", "photo")) files_to_download.append(("2018/07/31/IMG_7409-medium.MOV", "photo")) files_to_skip.append(("2018/07/30/IMG_7408.JPG", "photo")) files_to_skip.append(("2018/07/30/IMG_7408-medium.MOV", "photo")) files_to_download.append(("2018/07/30/IMG_7407.JPG", "photo")) files_to_download.append(("2018/07/30/IMG_7407-medium.MOV", "photo")) files_to_skip.append(("2018/07/30/IMG_7405.MOV", "video")) files_to_skip.append(("2018/07/30/IMG_7404.MOV", "video")) files_to_download.append(("2018/07/30/IMG_7403.MOV", "video")) files_to_download.append(("2018/07/30/IMG_7402.MOV", "video")) files_to_skip.append(("2018/07/30/IMG_7401.MOV", "photo")) files_to_skip.append(("2018/07/30/IMG_7400.JPG", "photo")) files_to_skip.append(("2018/07/30/IMG_7400-medium.MOV", "photo")) files_to_skip.append(("2018/07/30/IMG_7399.JPG", "photo")) files_to_download.append(("2018/07/30/IMG_7399-medium.MOV", "photo")) for f in files_to_skip: open("%s/%s" % (base_dir, f[0]), "a").close() with mock.patch("icloudpd.download.download_media") as dp_patched: dp_patched.return_value = True with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--live-photo-size", "medium", "--until-found", "3", "--recent", "20", "--no-progress-bar", base_dir, ], ) print_result_exception(result) expected_calls = list( map( lambda f: call( ANY, ANY, "%s/%s" % (base_dir, f[0]), "mediumVideo" if ( f[1] == 'photo' and f[0].endswith('.MOV') ) else "original"), files_to_download, ) ) dp_patched.assert_has_calls(expected_calls) self.assertIn( "DEBUG Looking up all photos and videos...", self._caplog.text ) self.assertIn( "INFO Downloading ??? original photos and videos to tests/fixtures/Photos/ ...", self._caplog.text, ) for f in files_to_skip: expected_message = "INFO %s/%s already exists." % (base_dir, f[0]) self.assertIn(expected_message, self._caplog.text) self.assertIn( "INFO Found 3 consecutive previously downloaded photos. Exiting", self._caplog.text, ) assert result.exit_code == 0
def test_download_and_skip_existing_photos(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos/2018/07/30/") open("tests/fixtures/Photos/2018/07/30/IMG_7408.JPG", "a").close() open("tests/fixtures/Photos/2018/07/30/IMG_7407.JPG", "a").close() with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "5", "--skip-videos", "--skip-live-photos", "--set-exif-datetime", "--no-progress-bar", "tests/fixtures/Photos", ], ) print_result_exception(result) self.assertIn("DEBUG Looking up all photos...", self._caplog.text) self.assertIn( "INFO Downloading 5 original photos to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO Downloading tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) self.assertNotIn( "IMG_7409.MOV", self._caplog.text, ) self.assertIn( "INFO tests/fixtures/Photos/2018/07/30/IMG_7408.JPG already exists.", self._caplog.text, ) self.assertIn( "INFO tests/fixtures/Photos/2018/07/30/IMG_7407.JPG already exists.", self._caplog.text, ) self.assertIn( "INFO Skipping IMG_7405.MOV, only downloading photos.", self._caplog.text, ) self.assertIn( "INFO Skipping IMG_7404.MOV, only downloading photos.", self._caplog.text, ) self.assertIn( "INFO All photos have been downloaded!", self._caplog.text ) # Check that file was downloaded self.assertTrue( os.path.exists("tests/fixtures/Photos/2018/07/31/IMG_7409.JPG")) # Check that mtime was updated to the photo creation date photo_mtime = os.path.getmtime("tests/fixtures/Photos/2018/07/31/IMG_7409.JPG") photo_modified_time = datetime.datetime.utcfromtimestamp(photo_mtime) self.assertEquals( "2018-07-31 07:22:24", photo_modified_time.strftime('%Y-%m-%d %H:%M:%S')) assert result.exit_code == 0
def test_download_and_skip_existing_photos(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos/2018/07/30/") open("tests/fixtures/Photos/2018/07/30/IMG_7408.JPG", "a").close() open("tests/fixtures/Photos/2018/07/30/IMG_7407.JPG", "a").close() with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "5", "--skip-videos", "--skip-live-photos", "--set-exif-datetime", "--no-progress-bar", "-d", "tests/fixtures/Photos", ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos from album All Photos...", self._caplog.text) self.assertIn( "INFO Downloading 5 original photos to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO Downloading tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) self.assertNotIn( "IMG_7409.MOV", self._caplog.text, ) self.assertIn( "INFO tests/fixtures/Photos/2018/07/30/IMG_7408.JPG already exists.", self._caplog.text, ) self.assertIn( "INFO tests/fixtures/Photos/2018/07/30/IMG_7407.JPG already exists.", self._caplog.text, ) self.assertIn( "INFO Skipping IMG_7405.MOV, only downloading photos.", self._caplog.text, ) self.assertIn( "INFO Skipping IMG_7404.MOV, only downloading photos.", self._caplog.text, ) self.assertIn("INFO All photos have been downloaded!", self._caplog.text) # Check that file was downloaded self.assertTrue( os.path.exists( "tests/fixtures/Photos/2018/07/31/IMG_7409.JPG")) # Check that mtime was updated to the photo creation date photo_mtime = os.path.getmtime( "tests/fixtures/Photos/2018/07/31/IMG_7409.JPG") photo_modified_time = datetime.datetime.utcfromtimestamp( photo_mtime) self.assertEquals( "2018-07-31 07:22:24", photo_modified_time.strftime('%Y-%m-%d %H:%M:%S')) assert result.exit_code == 0
def test_until_found(self): base_dir = "tests/fixtures/Photos" if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos/2018/07/30/") os.makedirs("tests/fixtures/Photos/2018/07/31/") files_to_download = [] files_to_skip = [] files_to_download.append(("2018/07/31/IMG_7409.JPG", "photo")) files_to_download.append(("2018/07/31/IMG_7409-medium.MOV", "photo")) files_to_skip.append(("2018/07/30/IMG_7408.JPG", "photo")) files_to_skip.append(("2018/07/30/IMG_7408-medium.MOV", "photo")) files_to_download.append(("2018/07/30/IMG_7407.JPG", "photo")) files_to_download.append(("2018/07/30/IMG_7407-medium.MOV", "photo")) files_to_skip.append(("2018/07/30/IMG_7405.MOV", "video")) files_to_skip.append(("2018/07/30/IMG_7404.MOV", "video")) files_to_download.append(("2018/07/30/IMG_7403.MOV", "video")) files_to_download.append(("2018/07/30/IMG_7402.MOV", "video")) files_to_skip.append(("2018/07/30/IMG_7401.MOV", "photo")) files_to_skip.append(("2018/07/30/IMG_7400.JPG", "photo")) files_to_skip.append(("2018/07/30/IMG_7400-medium.MOV", "photo")) files_to_skip.append(("2018/07/30/IMG_7399.JPG", "photo")) files_to_download.append(("2018/07/30/IMG_7399-medium.MOV", "photo")) for f in files_to_skip: open("%s/%s" % (base_dir, f[0]), "a").close() with mock.patch("icloudpd.download.download_media") as dp_patched: dp_patched.return_value = True with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ[ "CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--live-photo-size", "medium", "--until-found", "3", "--recent", "20", "--no-progress-bar", "-d", base_dir, ], ) print_result_exception(result) expected_calls = list( map( lambda f: call( ANY, ANY, "%s/%s" % (base_dir, f[0]), "mediumVideo" if (f[1] == 'photo' and f[0].endswith('.MOV')) else "original"), files_to_download, )) dp_patched.assert_has_calls(expected_calls) self.assertIn( "DEBUG Looking up all photos and videos from album All Photos...", self._caplog.text) self.assertIn( "INFO Downloading ??? original photos and videos to tests/fixtures/Photos/ ...", self._caplog.text, ) for f in files_to_skip: expected_message = "INFO %s/%s already exists." % ( base_dir, f[0]) self.assertIn(expected_message, self._caplog.text) self.assertIn( "INFO Found 3 consecutive previously downloaded photos. Exiting", self._caplog.text, ) assert result.exit_code == 0
def test_download_photos_and_set_exif(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos/2018/07/30/") open("tests/fixtures/Photos/2018/07/30/IMG_7408.JPG", "a").close() open("tests/fixtures/Photos/2018/07/30/IMG_7407.JPG", "a").close() # Download the first photo, but mock the video download orig_download = PhotoAsset.download def mocked_download(self, size): if not hasattr(PhotoAsset, "already_downloaded"): response = orig_download(self, size) setattr(PhotoAsset, "already_downloaded", True) return response return mock.MagicMock() with mock.patch.object(PhotoAsset, "download", new=mocked_download): with mock.patch("icloudpd.exif_datetime.get_photo_exif" ) as get_exif_patched: get_exif_patched.return_value = False with vcr.use_cassette( "tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ[ "CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "4", "--set-exif-datetime", # '--skip-videos', # "--skip-live-photos", "--no-progress-bar", "-d", "tests/fixtures/Photos", ], ) print_result_exception(result) self.assertIn( "DEBUG Looking up all photos and videos from album All Photos...", self._caplog.text, ) self.assertIn( "INFO Downloading 4 original photos and videos to tests/fixtures/Photos/ ...", self._caplog.text, ) self.assertIn( "INFO Downloading tests/fixtures/Photos/2018/07/31/IMG_7409.JPG", self._caplog.text, ) # YYYY:MM:DD is the correct format. self.assertIn( "DEBUG Setting EXIF timestamp for tests/fixtures/Photos/2018/07/31/IMG_7409.JPG: 2018:07:31", self._caplog.text, ) self.assertIn("INFO All photos have been downloaded!", self._caplog.text) assert result.exit_code == 0
def test_handle_session_error_during_photo_iteration(self): if os.path.exists("tests/fixtures/Photos"): shutil.rmtree("tests/fixtures/Photos") os.makedirs("tests/fixtures/Photos") with vcr.use_cassette("tests/vcr_cassettes/listing_photos.yml"): # Pass fixed client ID via environment variable os.environ["CLIENT_ID"] = "DE309E26-942E-11E8-92F5-14109FE0B321" def mock_raise_response_error(offset): raise PyiCloudAPIResponseError("Invalid global session", 100) with mock.patch("time.sleep") as sleep_mock: with mock.patch.object(PhotoAlbum, "photos_request") as pa_photos_request: pa_photos_request.side_effect = mock_raise_response_error # Let the initial authenticate() call succeed, # but do nothing on the second try. orig_authenticate = PyiCloudService.authenticate def mocked_authenticate(self): if not hasattr(self, "already_authenticated"): orig_authenticate(self) setattr(self, "already_authenticated", True) with mock.patch.object( PyiCloudService, "authenticate", new=mocked_authenticate ): runner = CliRunner() result = runner.invoke( main, [ "--username", "*****@*****.**", "--password", "password1", "--recent", "1", "--skip-videos", "--skip-live-photos", "--no-progress-bar", "tests/fixtures/Photos", ], ) print_result_exception(result) # Error msg should be repeated 5 times assert ( self._caplog.text.count( "Session error, re-authenticating..." ) == 5 ) self.assertIn( "INFO iCloud re-authentication failed! Please try again later.", self._caplog.text, ) # Make sure we only call sleep 4 times (skip the first retry) self.assertEquals(sleep_mock.call_count, 4) assert result.exit_code == -1