"""Unit tests for the tasks.""" import os from unittest.mock import call from unittest.mock import MagicMock from unittest.mock import patch from freezegun import freeze_time import pytest from activity_detector.flows.chartmetric_spike_detector import tasks @pytest.yield_fixture def mock_session_context(): """Yield session context.""" pth = 'activity_detector.flows.chartmetric_spike_detector.tasks.get_session' # noqa:E501 with patch(pth) as gt_session: mock_session_context = \ gt_session.return_value.__enter__.return_value yield mock_session_context def _query_file_contents(filename): """Load contents of query file. Args: filename (str): filename to load (path assumed) Returns: str: contents of file """ f = open( os.path.join( 'activity_detector', 'flows', 'chartmetric_spike_detector', 'queries', filename ), 'r' ) query = f.read() f.close() return query @patch.object(tasks, '_load_data_format_query', return_value='SELECT 1') def test_load_youtube_data(mock_query_load, mock_session_context): """Test load youtube data into social spike table. Args: mock_session_context (MagicMock): mock of snowflake conn session """ mock_session_context.execute.return_value.fetchone.return_value = {'c': 0} result = tasks.load_youtube_data(MagicMock(), '2019-06-15') assert result == dict() assert mock_session_context.execute.call_count == 2 expected_calls = [ call( _query_file_contents('check_activity_exists.sql'), {'date': '2019-06-15', 'social': 'youtube'} ), call().fetchone(), call( 'SELECT 1', {'date': '2019-06-15'} ) ] mock_session_context.execute.assert_has_calls(expected_calls) @patch.object(tasks, '_load_data_format_query', return_value='SELECT 1') def test_load_instagram_data(mock_query_load, mock_session_context): """Test load instagram data into social spike table. Args: mock_session_context (MagicMock): mock of snowflake conn session """ mock_session_context.execute.return_value.fetchone.return_value = {'c': 0} result = tasks.load_instagram_data(MagicMock(), '2019-06-15') assert result == dict() assert mock_session_context.execute.call_count == 2 expected_calls = [ call( _query_file_contents('check_activity_exists.sql'), {'date': '2019-06-15', 'social': 'instagram'} ), call().fetchone(), call( 'SELECT 1', {'date': '2019-06-15'} ) ] mock_session_context.execute.assert_has_calls(expected_calls) def test_load_data_already_inserted(mock_session_context): """Test prevent double data load for date. Args: mock_session_context (MagicMock): mock of snowflake conn session """ mock_session_context.execute.return_value.fetchone.return_value = {'c': 10} result = tasks.load_instagram_data(MagicMock(), '2019-06-15') assert result == dict() assert mock_session_context.execute.call_count == 1 expected_calls = [ call( _query_file_contents('check_activity_exists.sql'), {'date': '2019-06-15', 'social': 'instagram'} ), call().fetchone() ] mock_session_context.execute.assert_has_calls(expected_calls) @patch('activity_detector.utils.ows_notifications.create_social_spike_notification') # noqa:E501 def test_detect_spikes(mock_push, mock_session_context): """Test read spike data and notification activity sent. Args: mock_push (MagicMock): mock of method sending data to ows-notifications mock_session_context (MagicMock): mock of snowflake conn session """ mock_session_context.execute.return_value.fetchall.return_value = [ { 'date': '2019-06-15', 'social': 'instagram', 'new_followers_today': 100, 'cm_artist_id': 12345 }, { 'date': '2019-06-15', 'social': 'youtube', 'new_followers_today': 200, 'cm_artist_id': 67890 } ] result = tasks.detect_spikes(MagicMock(), '2019-06-15') assert result == {'failures': 0} assert mock_session_context.execute.call_count == 1 expected_calls = [ call( _query_file_contents('get_activity.sql'), { 'date': '2019-06-15', 'youtube_min_score': 3, 'youtube_min_new_followers': 1000, 'instagram_min_score': 3, 'instagram_min_new_followers': 1000 } ) ] mock_session_context.execute.assert_has_calls(expected_calls) assert mock_push.call_count == 2 expected_calls = [ call('2019-06-15', 'instagram', 100, 12345), call().__bool__(), call('2019-06-15', 'youtube', 200, 67890), call().__bool__() ] mock_push.assert_has_calls(expected_calls) @patch('activity_detector.utils.ows_notifications.create_social_spike_notification') # noqa:E501 def test_detect_spikes_fail_count(mock_push, mock_session_context): """Test read spike data and notification activity sent with failures. Args: mock_push (MagicMock): mock of method sending data to ows-notifications mock_session_context (MagicMock): mock of snowflake conn session """ mock_push.return_value = False mock_session_context.execute.return_value.fetchall.return_value = [ { 'date': '2019-06-15', 'social': 'instagram', 'new_followers_today': 100, 'cm_artist_id': 12345 } ] result = tasks.detect_spikes(MagicMock(), '2019-06-15') assert result == {'failures': 1} @pytest.mark.parametrize( ('should_run', 'response'), [ (True, None), (True, {'status': 'PROCESSED'}), (True, {}), (False, {'status': 'PROCESSED_NOTIF_SENT'}) ] ) def test_check_dynamo_status(should_run, response): """Test dynamodb status checker. Args: should_run (bool): expected output after reading status from db response (dict): response from db """ with patch('activity_detector.utils.dynamodb.get_status') as status: status.return_value = response results = tasks.check_dynamo_status(MagicMock(), '2019-06-15') assert results == {'should_run': should_run} @pytest.mark.parametrize( ('status', 'failures'), [ ('PROCESSED', 1), ('PROCESSED_NOTIF_SENT', 0) ] ) def test_set_dynamo_status(status, failures): """Test saving status after flow run. Args: status (str): status set in db failures (str): number of failures when sending to ows-notifications """ with patch('activity_detector.utils.dynamodb.set_status') as set_status: tasks.set_dynamo_status(MagicMock(), '2019-06-15', failures) assert set_status.call_args == call( 'chartmetric_spike_detector', '2019-06-15', status) def test_bootstrap(): """Assert bootstrap information in returned as expected.""" results = tasks.bootstrap(MagicMock(), '2019-06-15', 'run-id') assert results == {'target_date': '2019-06-15'} @freeze_time('2019-06-15') def test_bootstrap_defaults(): """Assert default bootstrap information is returned as expected.""" results = tasks.bootstrap(MagicMock(), None, 'run-id') assert results == {'target_date': '2019-06-15'} @pytest.mark.parametrize( ('social', 'output'), [ ('youtube', 'youtube_channel_stat SUBSCRIBERS youtube'), ('instagram', 'instagram_stat FOLLOWERS instagram'), ] ) @patch.object(tasks, 'sql_loader') def test_load_data_format_query(mock_load_query, social, output): """Test query formatting is reading config correctly.""" mock_load_query.load_query.return_value = '{cm_table} {cm_column} {social}' result = tasks._load_data_format_query(social) assert result == output