# pylint: disable=too-many-arguments, too-many-locals, from datetime import datetime, timezone from typing import Any, Dict, List from unittest import mock import pytest from db_schema.postgres.connection import Connection from db_schema.schemas import slz from mypy_boto3_stepfunctions import SFNClient from smelog.factory import SmeBoundLogger from slz_spotify_charts_clean_up.const import Environment from slz_spotify_charts_clean_up.entities import Config from slz_spotify_charts_clean_up.manager import Manager from slz_spotify_charts_clean_up.services.db import DBService from slz_spotify_charts_clean_up.services.file_storage import FileStorageService from slz_spotify_charts_clean_up.services.job import JobService from tests.conftest import CommonValues, CSSet, UoWSet pytestmark = [pytest.mark.integration] @pytest.mark.parametrize( 'unit_to_test, control_unit, unit_of_work__indirect, content_status__indirect,' 'expected_result, expected_sfn_call', [ ( UoWSet.DEFAULT, UoWSet.REPROCESSING, [UoWSet.DEFAULT, UoWSet.REPROCESSING], [ (UoWSet.DEFAULT, CSSet.COMPLETE_JOB_1), (UoWSet.DEFAULT, CSSet.MISSING_JOB_1), ], [CommonValues.job_1], [ mock.call(executionArn=f'{CommonValues.step_function_arn}:{CommonValues.job_1}'), ], ), ( UoWSet.DEFAULT, UoWSet.REPROCESSING, [UoWSet.DEFAULT, UoWSet.REPROCESSING], [ (UoWSet.DEFAULT, CSSet.COMPLETE_JOB_1), (UoWSet.DEFAULT, CSSet.MISSING_JOB_1), (UoWSet.DEFAULT, CSSet.COMPLETE_JOB_2), ], [CommonValues.job_1, CommonValues.job_2], [ mock.call(executionArn=f'{CommonValues.step_function_arn}:{CommonValues.job_1}'), mock.call(executionArn=f'{CommonValues.step_function_arn}:{CommonValues.job_2}'), ], ), ( UoWSet.DEFAULT, UoWSet.REPROCESSING, [UoWSet.DEFAULT, UoWSet.REPROCESSING], [ (UoWSet.DEFAULT, CSSet.COMPLETE_JOB_1), (UoWSet.DEFAULT, CSSet.MISSING_JOB_1), (UoWSet.DEFAULT, CSSet.COMPLETE_JOB_2), (UoWSet.REPROCESSING, CSSet.COMPLETE_JOB_3), ], [CommonValues.job_1, CommonValues.job_2], [ mock.call(executionArn=f'{CommonValues.step_function_arn}:{CommonValues.job_1}'), mock.call(executionArn=f'{CommonValues.step_function_arn}:{CommonValues.job_2}'), ], ), ], indirect=['unit_of_work__indirect', 'content_status__indirect'], ) @pytest.mark.freeze_time('2021-10-21') def test_process( common_values: CommonValues, db: Connection, unit_of_work_stubs: Dict[UoWSet, Dict[str, Any]], unit_to_test: UoWSet, control_unit: UoWSet, unit_of_work__indirect: Dict[UoWSet, slz.UnitOfWork], content_status__indirect: Dict[UoWSet, Dict[CSSet, slz.ContentStatus]], expected_result: List[str], expected_sfn_call: List[Any], ): uow = unit_of_work__indirect[unit_to_test] control_uow = unit_of_work__indirect[control_unit] content_statuses = content_status__indirect buckets_to_clean = [common_values.corrupted_bucket, common_values.decompressed_bucket] step_functions_client_mock = mock.MagicMock(spec=SFNClient) step_functions_client_mock.stop_execution.return_value = 'SFN Response' config = Config( environment=Environment.DEV, sentry_secret_key='', buckets_to_clean=buckets_to_clean, step_function_arn=common_values.step_function_arn, rds_secret_key='rds/key', ) manager = Manager( config=config, logger=mock.MagicMock(spec=SmeBoundLogger), db_service=DBService( logger=mock.MagicMock(spec=SmeBoundLogger), connection=db, operation_timestamp=datetime.now(timezone.utc), ), job_service=JobService( logger=mock.MagicMock(spec=SmeBoundLogger), client=step_functions_client_mock, state_machine_arn=config.step_function_arn, current_execution_arn=common_values.current_execution_arn, ), file_storage=mock.MagicMock(spec=FileStorageService), ) assert uow.activity_status.value == unit_of_work_stubs[unit_to_test]['activity_status'] assert uow.completeness_status.value == unit_of_work_stubs[unit_to_test]['completeness_status'] assert uow.last_updated_at == unit_of_work_stubs[unit_to_test]['last_updated_at'] # Check initial state of control unit assert control_uow.activity_status.value == unit_of_work_stubs[control_unit]['activity_status'] assert control_uow.\ completeness_status.value == unit_of_work_stubs[control_unit]['completeness_status'] assert control_uow.last_updated_at == unit_of_work_stubs[control_unit]['last_updated_at'] result = manager.process(uow, common_values.file_prefix) assert sorted(result['terminated_jobs']) == sorted(expected_result) # Check that UoW is actually cancelled assert uow.activity_status == slz.ActivityStatusEnum.NOT_IN_PROGRESS assert uow.completeness_status == slz.CompletenessStatusEnum.CANCELLED assert uow.last_updated_at.isoformat() == datetime.now(timezone.utc).isoformat() # Check control unit unchanged assert control_uow.activity_status.value == unit_of_work_stubs[control_unit]['activity_status'] assert control_uow.\ completeness_status.value == unit_of_work_stubs[control_unit]['completeness_status'] assert control_uow.last_updated_at == unit_of_work_stubs[control_unit]['last_updated_at'] # Check that signal to stop specified jobs was sent step_functions_client_mock.stop_execution.assert_has_calls(expected_sfn_call, any_order=True) # Check that all content statuses are stopped. for content_status in content_statuses[unit_to_test].values(): assert content_status.content_status == slz.ContentStatusEnum.CANCELLED # Check content statuses of a control UoW for content_status in content_statuses[control_unit].values(): assert content_status.content_status != slz.ContentStatusEnum.CANCELLED