"""Unit tests for tasks of iTunes Hides Workflow.""" from datetime import datetime from importlib import reload from unittest import mock from unittest.mock import MagicMock from unittest.mock import patch from boto3.exceptions import S3UploadFailedError from freezegun import freeze_time from garcon_contrib.dynamo_feed_status import garcon_feed_status import pytest from feed_ingestion import tasks as common_tasks from feed_ingestion.flows.itunes_hides import config from feed_ingestion.flows.itunes_hides import tasks _date = '2018-02-12' @pytest.fixture(autouse=True) def mock_check_status(request, monkeypatch): """Mock check_status decorator. Mock so that the decorated function is kept intact and always get called. """ mock = MagicMock() # just return the function without any modifications mock.return_value = lambda f: f monkeypatch.setattr(common_tasks, 'check_status', value=mock) reload(tasks) # redecorate tasks yield monkeypatch.undo() reload(tasks) @pytest.fixture def mock_set_overall_status(): """Yield overall status.""" overall_status_path = ( 'feed_ingestion.flows.itunes_hides.tasks.garcon_feed_status.' 'set_overall_status') with patch(overall_status_path) as overall_status: yield overall_status @pytest.fixture def mock_get_overall_status(): """Yield overall status.""" overall_status_path = ( 'feed_ingestion.flows.itunes_hides.tasks.garcon_feed_status.' 'get_overall_status') with patch(overall_status_path) as overall_status: yield overall_status @pytest.fixture def expected_bootstrap_response(): """Response for bootstrap task.""" return { 'feed_name': config.feed_name, 'secrets_path': config.secrets_path, 'file_pattern': r'.*\.zip', 'archive_path': 's3://dev-cucumbers/' 'iTunesHides/archives/{}/'.format(_date), 'download_path': 's3://dev-feed-drop/filedrop/ops/hides/', 'preprocessed_path': 's3://dev-cucumbers/' 'iTunesHides/preprocessed/{}/' 'preprocessed_hides.csv.gz'.format(_date), 'staging_raw_table': 'itunes_hidden', 'temp_staging_raw_table': 'temp_itunes_hidden_20180212', 'date': _date, 'snapshot_table': 'itunes_hidden_master', 'snapshots_amount': 1} def test_bootstrap(expected_bootstrap_response): """Bootstrap should return expected response.""" result = tasks.bootstrap(MagicMock(), date=_date, reload=False) assert result == expected_bootstrap_response class TestProcessDropFiles(object): """Test process_drop_files.""" @pytest.fixture def mock_s3_get_source_files_content(self): """Mock get_source_files_content.""" path = ( 'feed_ingestion.flows.itunes_hides' '.tasks.s3.get_source_files_content') with patch(path) as mock_s3_get_source_files_content: mock_s3_get_source_files_content.return_value = [ ('test_HiddenMusic.qwe123.zip', b'')] yield mock_s3_get_source_files_content @pytest.fixture def mock_zipfile(self): """Mock zipfile module.""" path = ( 'feed_ingestion.flows.itunes_hides' '.tasks.zipfile.ZipFile') with patch(path) as mock_zipfile: mock_zipfile_obj = MagicMock() mock_zipfile_obj.namelist.return_value = [b''] mock_zipfile.return_value.__enter__.return_value = ( mock_zipfile_obj) yield mock_zipfile_obj @pytest.fixture def mock_get_hides(self): """Mock _get_hides.""" path = ( 'feed_ingestion.flows.itunes_hides.tasks._get_hides') with patch(path) as mock_get_hides: mock_get_hides.return_value = [] yield mock_get_hides @pytest.fixture def context(self): """Process drop files context.""" return { 'activity': MagicMock(), 'feed_name': config.feed_name, 'date': _date, 's3_archive_path': 's3://dev-cucumbers/' 'iTunesHides/archives/{}/'.format(_date), 's3_preprocessed_path': 's3://dev-cucumbers/' 'iTunesHides/preprocessed/{}/' 'preprocessed_hides.csv.gz'.format(_date), 'source_files_dict': { 'files': ['test_HiddenMusic.qwe123.zip'] } } @patch('feed_ingestion.flows.itunes_hides.tasks._create_csv') @patch('feed_ingestion.flows.itunes_hides' '.tasks.s3.upload_processed_to_s3') def test_process_drop_files( self, mock_upload_processed_to_s3, mock_create_csv, mock_set_overall_status, mock_s3_get_source_files_content, mock_get_hides, mock_zipfile, context): """Test process_drop_files.""" tasks.process_drop_files(**context) mock_s3_get_source_files_content.assert_called_with( context['s3_archive_path'], context['source_files_dict'], encoding=None) assert mock_get_hides.called mock_create_csv.assert_has_calls([ mock.call(mock.ANY, config.fields, '\t'), ]) mock_upload_processed_to_s3.assert_has_calls([ mock.call(mock.ANY, context['s3_preprocessed_path'], expected_bucket_owner=config.expected_bucket_owner), ]) @patch('feed_ingestion.flows.itunes_hides.tasks._create_csv') @patch('feed_ingestion.flows.itunes_hides' '.tasks.s3.upload_processed_to_s3', side_effect=S3UploadFailedError) def test_process_drop_files_fail( self, mock_upload_processed_to_s3, mock_create_csv, mock_set_overall_status, mock_s3_get_source_files_content, mock_get_hides, mock_zipfile, context): """Test process_drop_files upload failure.""" with pytest.raises(S3UploadFailedError): tasks.process_drop_files(**context) mock_set_overall_status.assert_called_with( config.feed_name, _date, garcon_feed_status.STATUS_NOT_AVAILABLE) @freeze_time('2019-03-01') def test_get_hides(self): """Test _get_hides.""" file = ( 'Apple Id UPC/EAN/JAN Grid Vendor ID Artist Album ' 'Genre Track Apple ID Track ISRC Track Name Total Tracks ' 'Total Discs Is Complete Provider ' 'Label Name Audio must be redelivered for iTunes Plus ' 'Reason Import Date Hide Date\n' '6499442 590570986120 ' '100692 Living Legends The Underworld Hip Hop/Rap 123 ASKSD ' 'World release 13 ' '1 True IODA Worldwide Massmedia/Revenge Ent. ' 'No Hidden: (legacy) 02-APR-2004\n' '6572513 693405000128 100685' ' The Grouch F*ck the Dumb Hip Hop/Rap 123 ASKSD World release ' '20 1 True IODA Legendary Music ' 'Yes Hidden: Developer Hidden 07-APR-2004 08-JAN-2007' ) hides_expected = [ { 'apple_id': '6499442', 'upc': '590570986120', 'grid': '', 'vendor_id': '100692', 'artist_name_via_itunes': 'Living Legends', 'album_name_via_itunes': 'The Underworld', 'genre': 'Hip Hop/Rap', 'track_apple_id': '123', 'track_isrc': 'ASKSD', 'track_name': 'World release', 'total_tracks': '13', 'total_discs': '1', 'is_complete': 1, 'content_owner': 'IODA', 'label_name': 'Worldwide Massmedia/Revenge Ent.', 'audio_must_be_redelivered_for_itunes_plus': 0, 'reason': 'LEGACY', 'import_date': datetime(2004, 4, 2, 0, 0), 'hide_date': None, 'days_until_hidden': None, 'original_reason': 'Hidden: (legacy)', 'appleid_reason': '6499442_LEGACY', 'ingest_date': _date }, { 'apple_id': '6572513', 'upc': '693405000128', 'grid': '', 'vendor_id': '100685', 'artist_name_via_itunes': 'The Grouch', 'album_name_via_itunes': 'F*ck the Dumb', 'genre': 'Hip Hop/Rap', 'track_apple_id': '123', 'track_isrc': 'ASKSD', 'track_name': 'World release', 'total_tracks': '20', 'total_discs': '1', 'is_complete': 1, 'content_owner': 'IODA', 'label_name': 'Legendary Music', 'audio_must_be_redelivered_for_itunes_plus': 1, 'reason': 'DEVELOPER HIDDEN', 'import_date': datetime(2004, 4, 7, 0, 0), 'hide_date': datetime(2007, 1, 8, 0, 0), 'days_until_hidden': 1006, 'original_reason': 'Hidden: Developer Hidden', 'appleid_reason': '6572513_DEVELOPER HIDDEN', 'ingest_date': _date } ] hides = tasks._get_hides(file, _date) assert hides == hides_expected @patch('feed_ingestion.flows.itunes_hides.tasks.s3.delete_s3_obj') def test_remove_files_from_path(mock_delete_s3_obj): """Test process_drop_files.""" tasks.remove_files_from_path( MagicMock(), date=_date, download_path='s3://dev-feed-drop/filedrop/ops/hides/', source_files_dict={ 'files': [{ 'file_name': 'test_HiddenMusic.qwe123.zip', 'found': True}]} ) mock_delete_s3_obj.assert_called_with( 'dev-feed-drop', 'filedrop/ops/hides/test_HiddenMusic.qwe123.zip') def test_load_snapshot_table(sf_config_mock): """Test load_snapshot_table task.""" activity_mock = MagicMock() executor_with_mock = MagicMock() executor_mock = MagicMock() executor_mock.__enter__.return_value = executor_with_mock executor_class_mock = MagicMock(return_value=executor_mock) with patch.dict(tasks.registered_executors, {'test_feed': executor_class_mock}): tasks.load_snapshot_table( activity_mock, '2017-08-02', sf_config_mock, 'test_feed', None, None, 'temp_table', 'snapshot_table', 1) executor_with_mock.clean_snapshot_table.assert_called_with( 'snapshot_table', '2017-08-02') executor_with_mock.load_snapshot_table.assert_called_with( 'temp_table', 'snapshot_table') executor_with_mock.remove_previous_snapshots.assert_called_with( 'snapshot_table', 1)