def test_find_unauthorized_additions_returns_only_unauthorized_additions( self): pl_id = self.generate_spotify_id() items = [{ 'track': { 'uri': self.generate_track_uri(), 'name': 'track_name' }, 'added_at': 'added_at_timestamp', 'added_by': { 'id': self.generate_spotify_id() }, 'position': index } for index in range(0, 200)] mock_api = spotipy.client.Spotify() mock_api.playlist = Mock(return_value={'name': 'playlist_name'}) cleaner = PlaylistCleaner(self.test_logger, mock_api, 'playlist_owner', {}) cleaner.spotify_helper.get_all_items_in_playlist = Mock( return_value=items) cleaner.playlist_addition_is_authorized = Mock( side_effect=[i not in [22, 60, 129] for i in range(0, len(items))]) result = cleaner.find_unauthorized_additions(pl_id) self.assertEqual(len(result), 3) self.assertEqual(result[0]['uri'], items[22]['track']['uri']) self.assertEqual(result[1]['uri'], items[60]['track']['uri']) self.assertEqual(result[2]['uri'], items[129]['track']['uri'])
def test_playlist_addition_is_authorized_returns_true_if_adder_is_playlist_owner_regardless_of_local_and_global_restrictions( self): cleaner = PlaylistCleaner(self.test_logger, Mock(), 'owner_id', {}) cleaner._local_authorization = Mock(return_value='unauthorized') cleaner._global_authorization = Mock(return_value='unauthorized') self.assertTrue( cleaner.playlist_addition_is_authorized( 'owner_id', self.generate_spotify_id()))
def test_playlist_addition_is_authorized_returns_false_if_global_mode_is_whitelist_and_adder_not_globally_whitelisted( self): pl_config = {'GLOBAL_MODE': 'whitelist', 'GLOBAL_WHITELIST': []} cleaner = PlaylistCleaner(self.test_logger, Mock(), 'owner_id', pl_config) self.assertFalse( cleaner.playlist_addition_is_authorized( 'adder_id', self.generate_spotify_id()))
def test_playlist_addition_is_authorized_returns_true_if_global_mode_is_blacklist_and_adder_not_globally_blacklisted( self): pl_config = {'GLOBAL_MODE': 'blacklist', 'GLOBAL_BLACKLIST': []} cleaner = PlaylistCleaner(self.test_logger, Mock(), 'owner_id', pl_config) self.assertTrue( cleaner.playlist_addition_is_authorized( 'adder_id', self.generate_spotify_id()))
def test_playlist_addition_is_authorized_returns_false_adder_is_locally_unauthorized_but_globally_authorized( self): # this spec/fixture means local authorization overrides global authorization! cleaner = PlaylistCleaner(self.test_logger, Mock(), 'owner_id', {}) cleaner._local_authorization = Mock(return_value='unauthorized') cleaner._global_authorization = Mock(return_value='authorized') self.assertFalse( cleaner.playlist_addition_is_authorized( 'adder_id', self.generate_spotify_id()))
def test_remove_playlist_items_processes_removes_items_in_blocks_of_100_items( self): mock_api = spotipy.client.Spotify() mock_api.playlist = Mock(return_value={'name': 'Playlist Name'}) mock_api.playlist_remove_specific_occurrences_of_items = Mock() pl_id = self.generate_spotify_id() cleaner = PlaylistCleaner(self.test_logger, mock_api, 'owner_id', {}) item_ids = [self.generate_spotify_id() for i in range(0, 230)] items = [{ 'uri': 'spotify:track:' + item_ids[index], 'name': item_ids[index], 'added_by': 'adder', 'added_at': 'date added', 'position': index } for index in range(0, len(item_ids))] expected_removed = [{ 'uri': item['uri'], 'positions': [item['position']] } for item in items] cleaner.remove_playlist_items(pl_id, items) self.assertEqual( mock_api.playlist_remove_specific_occurrences_of_items.call_count, 3) self.assertEqual( len(mock_api.playlist_remove_specific_occurrences_of_items. call_args_list[0][0]), 2) self.assertEqual( mock_api.playlist_remove_specific_occurrences_of_items. call_args_list[0][0][0], pl_id) self.assertEqual( mock_api.playlist_remove_specific_occurrences_of_items. call_args_list[0][0][1], expected_removed[0:100]) self.assertEqual( len(mock_api.playlist_remove_specific_occurrences_of_items. call_args_list[1][0]), 2) self.assertEqual( mock_api.playlist_remove_specific_occurrences_of_items. call_args_list[1][0][0], pl_id) self.assertEqual( mock_api.playlist_remove_specific_occurrences_of_items. call_args_list[1][0][1], expected_removed[100:200]) self.assertEqual( len(mock_api.playlist_remove_specific_occurrences_of_items. call_args_list[2][0]), 2) self.assertEqual( mock_api.playlist_remove_specific_occurrences_of_items. call_args_list[2][0][0], pl_id) self.assertEqual( mock_api.playlist_remove_specific_occurrences_of_items. call_args_list[2][0][1], expected_removed[200:230])
def test_get_playlist_config_returns_none_if_playlist_has_no_config(self): config = { 'PROTECTED_PLAYLISTS': [{ 'myplaylist': { 'uri': self.generate_playlist_uri() } }] } cleaner = PlaylistCleaner(self.test_logger, self.test_api, 'owner_id', config) different_playlist = self.generate_spotify_id() self.assertIsNone(cleaner._get_playlist_config(different_playlist))
def test_playlist_addition_is_authorized_returns_false_if_adder_not_locally_authorized_and_global_mode_is_unknown( self): pl_config = { 'GLOBAL_MODE': 'not a valid mode', 'GLOBAL_WHITELIST': [], 'GLOBAL_BLACKLIST': [] } cleaner = PlaylistCleaner(self.test_logger, Mock(), 'owner_id', pl_config) cleaner._local_authorization = Mock(return_value='neutral') self.assertFalse( cleaner.playlist_addition_is_authorized( 'adder_id', self.generate_spotify_id()))
def test_playlist_addition_is_authorized_returns_false_playlist_has_no_local_or_global_restrictions( self): pl_id = self.generate_spotify_id() pl_config = { 'PROTECTED_PLAYLISTS': [{ 'myplaylist': { 'uri': 'spotify:playlist' + pl_id } }] } cleaner = PlaylistCleaner(self.test_logger, Mock(), 'owner_id', pl_config) self.assertFalse( cleaner.playlist_addition_is_authorized('adder_id', pl_id))
def test_playlist_addition_is_authorized_returns_false_if_playlist_has_only_local_whitelist_and_adder_not_whitelisted( self): pl_id = self.generate_spotify_id() pl_config = { 'PROTECTED_PLAYLISTS': [{ 'myplaylist': { 'uri': 'spotify:playlist:' + pl_id, 'whitelist': [] } }] } cleaner = PlaylistCleaner(self.test_logger, Mock(), 'owner_id', pl_config) self.assertFalse( cleaner.playlist_addition_is_authorized('adder_id', pl_id))
def moderate_playlists(logger, api_client, username, playlist_config): playlist_cleaner = PlaylistCleaner(logger, api_client, username, playlist_config) integrity_manager = IntegrityManager(logger, api_client, playlist_config) sp_helper = SpotifyHelper(logger) def protect_playlists(): # runs one iteration of playlist moderation if playlist_config['PROTECT_ALL']: protected_playlists = sp_helper.get_all_collab_playlists( username, api=api_client) else: protected_playlists = [] for playlist in playlist_config['PROTECTED_PLAYLISTS']: if len(playlist.keys()) == 1: for key, val in playlist.items(): protected_playlists.append(val) for playlist in protected_playlists: print( '') # newlines between playlists improves readibility of logs playlist_cleaner.run(playlist) integrity_manager.run(playlist) if '--loop' in sys.argv or '-l' in sys.argv: # For termination of loop mode, the idea is: delays between loop iterations are implemented # by a timeboxed attempt to get user input (from stdin) in order to allow the user to # terminate the program loop without needing to send a kill signal while True: protect_playlists() logger.info('Completed iteration') if user_wants_to_exit(playlist_config['DELAY_BETWEEN_SCANS']): break else: protect_playlists()
def test_playlist_addition_is_authorized_returns_true_if_adder_both_locally_explicitly_not_whitelisted_and_not_blacklisted( self): # this spec/fixture means blacklist overrides whitelist if a playlist configuration has both! pl_id = self.generate_spotify_id() pl_config = { 'PROTECTED_PLAYLISTS': [{ 'myplaylist': { 'uri': 'spotify:playlist:' + pl_id, 'blacklist': [], 'whitelist': [] } }] } cleaner = PlaylistCleaner(self.test_logger, Mock(), 'owner_id', pl_config) self.assertTrue( cleaner.playlist_addition_is_authorized('adder_id', pl_id))
def test_get_playlist_config_returns_config_of_playlist_with_matching_url( self): pl_id = self.generate_spotify_id() other_pl_uri = self.generate_playlist_uri() config = { 'PROTECTED_PLAYLISTS': [{ 'myplaylist1': { 'uri': other_pl_uri, 'other_config': '...' } }, { 'myplaylist2': { 'uri': 'spotify:playlist:' + pl_id, 'other_config': 'other relevant config data' } }] } cleaner = PlaylistCleaner(self.test_logger, self.test_api, 'owner_id', config) self.assertEqual(cleaner._get_playlist_config(pl_id), config['PROTECTED_PLAYLISTS'][1]['myplaylist2'])
def test_run_removes_only_unauthorized_items(self): mock_api = spotipy.client.Spotify() mock_api.playlist = Mock(return_value={'name': 'myplaylist'}) pl_id = self.generate_spotify_id() config = { 'PROTECT_ALL': False, 'PROTECTED_PLAYLISTS': [{ 'uri': 'spotify:playlist:' + pl_id }] } cleaner = PlaylistCleaner(self.test_logger, mock_api, 'playlist_owner', config) unauth_items = [{ 'name': 'unauth_item_1', 'uri': self.generate_track_uri() }, { 'name': 'unauth_item_2', 'uri': self.generate_track_uri() }] cleaner.find_unauthorized_additions = Mock(return_value=unauth_items) cleaner.remove_playlist_items = Mock() cleaner.run({'uri': 'spotify:playlist:' + pl_id}) self.assertEqual(len(cleaner.find_unauthorized_additions.call_args[0]), 1) cleaner.find_unauthorized_additions.called_once_with(pl_id)
def test_playlist_addition_is_authorized_ignores_global_blacklist_if_global_mode_is_whitelist( self): pl_config = { 'GLOBAL_MODE': 'whitelist', 'GLOBAL_WHITELIST': ['adder_id'], 'GLOBAL_BLACKLIST': ['adder_id'] } cleaner = PlaylistCleaner(self.test_logger, Mock(), 'owner_id', pl_config) self.assertTrue( cleaner.playlist_addition_is_authorized( 'adder_id', self.generate_spotify_id())) pl_config = { 'GLOBAL_MODE': 'whitelist', 'GLOBAL_WHITELIST': [], 'GLOBAL_BLACKLIST': [] } cleaner = PlaylistCleaner(self.test_logger, Mock(), 'owner_id', pl_config) self.assertFalse( cleaner.playlist_addition_is_authorized( 'adder_id', self.generate_spotify_id()))
def test_playlist_addition_is_authorized_returns_true_if_adder_is_playlist_owner( self): cleaner = PlaylistCleaner(self.test_logger, Mock(), 'owner_id', {}) self.assertTrue( cleaner.playlist_addition_is_authorized( 'owner_id', self.generate_spotify_id()))