import copy import datetime import json from unittest import mock from unittest.mock import MagicMock import pytest from processing_accounting.util import logging from processing_accounting.util import swf def monkeypatch_dependencies(monkeypatch): """Patch utility modules to simplify tests. Args: monkeypatch (_pytest.monkeypatch.monkeypatch): py.test monkeypatcher """ monkeypatch.setattr(logging.logger, 'info', MagicMock(return_value=None)) monkeypatch.setattr(swf.time, 'sleep', MagicMock(return_value=None)) @pytest.fixture def mock_workflow_executions_all_complete(): """Provides mock workflow executions list with execution status completed. """ mock_executions = [] for i in range(5): mock_execution = MagicMock() mock_execution.describe = MagicMock( return_value={'executionInfo': { 'closeStatus': 'COMPLETED', 'execution': { 'workflowId': 'workflow id for execution {}'.format(i) }}}) mock_executions.append(mock_execution) return mock_executions @pytest.fixture def mock_workflow_executions_some_pending(): """Provides mock workflow executions list with execution status completed. """ mock_executions = [] for i in range(5): status = 'COMPLETED' if i % 2 == 0: status = 'PENDING' mock_execution = MagicMock() mock_execution.describe = MagicMock( return_value={'executionInfo': { 'closeStatus': status, 'execution': { 'workflowId': 'workflow id for execution {}'.format(i) }}}) mock_executions.append(mock_execution) return mock_executions @pytest.fixture def mock_workflow_executions_some_invalid_state(): """Provides mock workflow executions list with execution status completed. """ mock_executions = [] for i in range(6): status = 'CANCELED' if i % 2 == 0: status = 'TERMINATED' elif i == 0: status = 'COMPLETED' elif i == 5: status = 'FAILED' mock_execution = MagicMock() mock_execution.describe = MagicMock( return_value={'executionInfo': { 'closeStatus': status, 'execution': { 'workflowId': 'workflow id for execution {}'.format(i) }}}) mock_executions.append(mock_execution) return mock_executions def test_wait_for_workflow_to_complete_all_completed( monkeypatch, mock_workflow_executions_all_complete): """Test wait_for_workflow_to_complete method with all workflow completed """ monkeypatch_dependencies(monkeypatch) resp = swf.wait_for_workflow_to_complete( mock_workflow_executions_all_complete) assert resp is None def test_wait_for_workflow_to_complete_some_pending( monkeypatch, mock_workflow_executions_some_pending): """Test wait_for_workflow_to_complete method with some workflow still running """ monkeypatch_dependencies(monkeypatch) with pytest.raises(Exception) as err: swf.wait_for_workflow_to_complete( mock_workflow_executions_some_pending, 3) assert str(err.value) == 'Wait time has exceeded.' def test_wait_for_workflow_to_complete_some_invalid_state( monkeypatch, mock_workflow_executions_some_invalid_state): """Test wait_for_workflow_to_complete method with some workflow still running """ monkeypatch_dependencies(monkeypatch) with pytest.raises(Exception) as err: swf.wait_for_workflow_to_complete( mock_workflow_executions_some_invalid_state, 3) assert str(err.value) == 'Workflow ends unexpectedly.' def test_get_swf(): """Test get_swf utility function.""" expected_result = 'test' session = MagicMock() session.client.return_value = expected_result assert swf.get_swf(session) == expected_result session.client.assert_called_once_with('swf') @mock.patch('processing_accounting.util.swf.get_swf') def test_get_latest_failed_executions(get_swf): """Test get_latest_failed_executions utility function.""" swf_mock = MagicMock() get_swf.return_value = swf_mock session = 'session' domain = 'test_domain' latest_date = datetime.datetime(2018, 1, 11) oldest_date = datetime.datetime(2018, 1, 1) days_rage = 10 next_page_token = 'next' execution_1 = {'execution': 1} execution_2 = {'execution': 2} expected_result = [execution_1, execution_2] swf_mock.list_closed_workflow_executions.side_effect = [ {'executionInfos': [execution_1], 'nextPageToken': next_page_token}, {'executionInfos': [execution_2]} ] first_call_args = { 'domain': domain, 'startTimeFilter': { 'oldestDate': oldest_date, 'latestDate': latest_date}, 'closeStatusFilter': {'status': 'FAILED'} } second_call_args = copy.deepcopy(first_call_args) second_call_args['nextPageToken'] = next_page_token expected_calls = [ mock.call(**first_call_args), mock.call(**second_call_args)] result = swf.get_latest_failed_executions( domain=domain, latest_date=latest_date, session=session, days_range=days_rage) assert expected_result == result get_swf.assert_called_once_with(session) swf_mock.list_closed_workflow_executions.assert_has_calls(expected_calls) @mock.patch('processing_accounting.util.swf.get_swf') def test_get_execution_history(get_swf): """Test get_execution_history utility function.""" domain = 'test_domain' execution = 'test_execution' session = 'test_session' expected_response = 'response' swf_mock = MagicMock() swf_mock.get_workflow_execution_history.return_value = expected_response get_swf.return_value = swf_mock result = swf.get_execution_history(domain, execution, session) assert expected_response == result get_swf.assert_called_once_with(session) swf_mock.get_workflow_execution_history.assert_called_once_with( domain=domain, execution=execution) def test_get_execution_input_empty(): """Test get_execution_input utility function.""" assert {} == swf.get_execution_input({}) def test_get_execution_input_no_attributes(): """Test get_execution_input utility function.""" execution_history = {'events': [{}]} assert {} == swf.get_execution_input(execution_history) def test_get_execution_input(): """Test get_execution_input utility function.""" expected_result = 'test_input' execution_history = { 'events': [ {'workflowExecutionStartedEventAttributes': { 'input': json.dumps(expected_result)}} ] } assert expected_result == swf.get_execution_input(execution_history) @mock.patch('processing_accounting.util.swf.get_swf') def test_get_execution_description(get_swf): """Test get_execution_description utility function.""" domain_name = 'test_domain' session = 'test_session' execution = 'test_execution' expected_response = 'response' swf_mock = MagicMock() swf_mock.describe_workflow_execution.return_value = expected_response get_swf.return_value = swf_mock result = swf.get_execution_description(execution, domain_name, session) assert expected_response == result get_swf.assert_called_once_with(session) swf_mock.describe_workflow_execution.assert_called_once_with( domain=domain_name, execution=execution) @mock.patch('processing_accounting.util.swf.get_execution_description') @mock.patch('processing_accounting.util.swf.get_swf') def test_re_run_execution(get_swf, get_execution_description): """Test re_run_execution utility function.""" expected_re_run_tags = ['re-run'] domain_name = 'test_domain' session = 'test_session' workflow_id = 'test workflow id' execution_data = {'workflowId': workflow_id} workflow_type = 'test custom export' execution = { 'execution': execution_data, 'workflowType': workflow_type } execution_input = 'test execution input' task_list = 'test task_list' task_start_to_close_timeout = 'test task_start_to_close_timeout' execution_start_to_close_timeout = 'test execution_start_to_close_timeout' child_policy = 'test child_policy' execution_configuration = { 'taskList': task_list, 'taskStartToCloseTimeout': task_start_to_close_timeout, 'executionStartToCloseTimeout': execution_start_to_close_timeout, 'childPolicy': child_policy, } execution_description = {'executionConfiguration': execution_configuration} get_execution_description.return_value = execution_description expected_response = 're run response' swf_mock = MagicMock() swf_mock.start_workflow_execution.return_value = expected_response get_swf.return_value = swf_mock result = swf.re_run_execution( execution, execution_input, domain_name, session) assert expected_response == result get_swf.assert_called_once_with(session) get_execution_description.assert_called_once_with( execution=execution_data, domain_name=domain_name, session=session) swf_mock.start_workflow_execution.assert_called_once_with( domain=domain_name, workflowId=workflow_id, workflowType=workflow_type, input=json.dumps(execution_input), taskList=task_list, taskStartToCloseTimeout=task_start_to_close_timeout, executionStartToCloseTimeout=execution_start_to_close_timeout, childPolicy=child_policy, tagList=expected_re_run_tags) @mock.patch('processing_accounting.util.swf.get_execution_description') @mock.patch('processing_accounting.util.swf.get_swf') def test_re_run_execution_exception(get_swf, get_execution_description): """Test re_run_execution utility function.""" swf_mock = MagicMock() swf_mock.exceptions.ClientError = Exception error_message = 'Test ClientError' swf_mock.start_workflow_execution.side_effect = Exception( error_message) get_swf.return_value = swf_mock expected_response = { 'ResponseMetadata': { 'HTTPStatusCode': 304, 'Message': error_message}} result = swf.re_run_execution( MagicMock(), 'execution_input', 'domain_name', 'session') assert expected_response == result assert swf_mock.start_workflow_execution.call_count == 1