"""Tests for action logic module.""" import datetime from unittest.mock import patch from oto import response import pytest from conflict_manager.api import app from conflict_manager.constants import database as db_consts from conflict_manager.constants import error as error_consts from conflict_manager.logic import action as action_logic from conflict_manager.utils import account_utils from conflict_manager.utils import api_utils mock_date = datetime.datetime(2017, 11, 14, 11, 19, 57, 12650).utcnow() expected_date = mock_date.strftime(db_consts.SNOWFLAKE_DATE_FORMAT) test_action_data = [ ( { 'release_action': { 'conflict_ids': [1, 2], 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', }, 'assert_action': { 'conflict_ids': [3, 4], 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', }, 'tuid': 123 }, [ { 'conflict_id': 1, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': '', 'tuid': 123 }, { 'conflict_id': 2, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': '', 'tuid': 123 }, { 'conflict_id': 3, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': '', 'tuid': 123 }, { 'conflict_id': 4, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': '', 'tuid': 123 } ] ), ( { 'release_action': { 'conflict_ids': [1, 2], 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', }, 'tuid': 123 }, [ { 'conflict_id': 1, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': '', 'tuid': 123 }, { 'conflict_id': 2, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': '', 'tuid': 123 } ] ), ( { 'assert_action': { 'conflict_ids': [3, 4], 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', }, 'tuid': 123 }, [ { 'conflict_id': 3, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': '', 'tuid': 123 }, { 'conflict_id': 4, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': '', 'tuid': 123 } ] ) ] TEST_USER_ID = 'alw:123' @pytest.fixture def patch_datetime_utcnow(monkeypatch): """Fixture for datetime.datetime.utcnow method.""" class mydatetime: """Mock datetime class for testing purposes.""" @classmethod def utcnow(cls): return mock_date monkeypatch.setattr(datetime, 'datetime', mydatetime) @pytest.mark.parametrize('data, expected_output', test_action_data) def test_format_all( patch_datetime_utcnow, data, expected_output, account_tuple, feature_engine): """Expect to convert data into proper form.""" action_date = datetime.datetime.utcnow() _, result = action_logic._format_actions(data, account_tuple, action_date) assert result == expected_output def test_validate_actions_success(mocker, feature_engine): """Expect success because data is valid.""" conflict_ids_return_value = api_utils.make_pagination_response( [1, 2]) mocker.patch.object( action_logic.fact_conflict, 'get_conflict_ids_for_account_and_isrc', return_value=conflict_ids_return_value) action_date = datetime.datetime.utcnow().strftime( db_consts.SNOWFLAKE_DATE_FORMAT) actions = [ { 'conflict_id': 1, 'action': 'release', 'action_date': action_date, 'reason': 'release test', 'additional_information': 'release test' }, { 'conflict_id': 2, 'action': 'release', 'action_date': action_date, 'reason': 'release test', 'additional_information': 'release test' }] account = account_utils.Account(123, 'vendor') validation_response = action_logic.validate_actions( account, 'TL1234567890', 'Def Jam', '2017-11-12', actions, 123) assert validation_response assert validation_response.message == actions def test_validate_actions_success_with_filtering(mocker, feature_engine): """Expect success because data is valid.""" conflict_ids_return_value = api_utils.make_pagination_response( [1, 2]) mocker.patch.object( action_logic.fact_conflict, 'get_conflict_ids_for_account_and_isrc', return_value=conflict_ids_return_value) action_date = datetime.datetime.utcnow().strftime( db_consts.SNOWFLAKE_DATE_FORMAT) actions = [ { 'conflict_id': 1, 'action': 'release', 'action_date': action_date, 'reason': 'release test', 'additional_information': 'release test' }, { 'conflict_id': 2, 'action': 'release', 'action_date': action_date, 'reason': 'release test', 'additional_information': 'release test' }] account = account_utils.Account(123, 'vendor') validation_response = action_logic.validate_actions( account, 'TL1234567890', 'Def Jam', '2017-11-12', actions, 123) assert validation_response assert validation_response.message == actions def test_validate_actions_not_exist_for_given_account(mocker, feature_engine): """Expect error because one of conflict_ids doesn't belong to account.""" conflict_ids_return_value = api_utils.make_pagination_response( [1]) mocker.patch.object( action_logic.fact_conflict, 'get_conflict_ids_for_account_and_isrc', return_value=conflict_ids_return_value) action_date = datetime.datetime.utcnow().strftime( db_consts.SNOWFLAKE_DATE_FORMAT) actions = [ { 'conflict_id': 1, 'action': 'release', 'action_date': action_date, 'reason': 'release test', 'additional_information': 'release test' }, { 'conflict_id': 2, 'action': 'release', 'action_date': action_date, 'reason': 'release test', 'additional_information': 'release test' }] account = account_utils.Account(123, 'vendor') validation_response = action_logic.validate_actions( account, 'TL1234567890', 'Def Jam', '2017-11-12', actions, 123) assert not validation_response assert validation_response.status == 400 assert ( validation_response.errors['message'] == error_consts.INVALID_TERRITORIES_MSG) def test_validate_actions_not_exist_for_given_account_with_filtering( mocker, feature_engine): """Expect error because one of conflict_ids doesn't belong to account.""" conflict_ids_return_value = api_utils.make_pagination_response( [1]) mocker.patch.object( action_logic.fact_conflict, 'get_conflict_ids_for_account_and_isrc', return_value=conflict_ids_return_value) action_date = datetime.datetime.utcnow().strftime( db_consts.SNOWFLAKE_DATE_FORMAT) actions = [ { 'conflict_id': 1, 'action': 'release', 'action_date': action_date, 'reason': 'release test', 'additional_information': 'release test' }, { 'conflict_id': 2, 'action': 'release', 'action_date': action_date, 'reason': 'release test', 'additional_information': 'release test' }] account = account_utils.Account(123, 'vendor') validation_response = action_logic.validate_actions( account, 'TL1234567890', 'Def Jam', '2017-11-12', actions, 123) assert not validation_response assert validation_response.status == 400 assert (validation_response.errors['message'] == error_consts.INVALID_TERRITORIES_MSG) def test_validate_actions_conflicts_not_match(mocker, feature_engine): """Expect error because conflicts ids in DB doesn't match action. len(actions) should match len(row_ids). """ conflict_ids_return_value = api_utils.make_pagination_response( [3, 2]) mocker.patch.object( action_logic.fact_conflict, 'get_conflict_ids_for_account_and_isrc', return_value=conflict_ids_return_value) action_date = datetime.datetime.utcnow().strftime( db_consts.SNOWFLAKE_DATE_FORMAT) actions = [ { 'conflict_id': 1, 'action': 'release', 'action_date': action_date, 'reason': 'release test', 'additional_information': 'release test' }] account = account_utils.Account(123, 'vendor') validation_response = action_logic.validate_actions( account, 'TL1234567890', 'Def Jam', '2017-11-12', actions, 123) assert not validation_response assert validation_response.status == 400 assert ( validation_response.errors['message'] == error_consts.INVALID_TERRITORIES_MSG) def test_validate_actions_conflicts_not_match_with_filtering( mocker, feature_engine): """Expect error because conflicts ids in DB doesn't match action. len(actions) should match len(row_ids). """ conflict_ids_return_value = api_utils.make_pagination_response( [3, 2]) mocker.patch.object( action_logic.fact_conflict, 'get_conflict_ids_for_account_and_isrc', return_value=conflict_ids_return_value) action_date = datetime.datetime.utcnow().strftime( db_consts.SNOWFLAKE_DATE_FORMAT) action = [ { 'conflict_id': 1, 'action': 'release', 'action_date': action_date, 'reason': 'release test', 'additional_information': 'release test' }] account = account_utils.Account(123, 'vendor') validation_response = action_logic.validate_actions( account, 'TL1234567890', 'Def Jam', '2017-11-12', action, 123) assert not validation_response assert validation_response.status == 400 assert ( validation_response.errors['message'] == error_consts.INVALID_TERRITORIES_MSG) def test_validate_actions_model_error(mocker): """Expect to handle error when fetching conflict ids.""" conflict_ids_return_value = response.create_error_response( code='something_bad', message='something bad') mocker.patch.object( action_logic.fact_conflict, 'get_conflict_ids_for_account_and_isrc', return_value=conflict_ids_return_value) action_date = datetime.datetime.utcnow().strftime( db_consts.SNOWFLAKE_DATE_FORMAT) actions = [ { 'conflict_id': 1, 'action': 'release', 'action_date': action_date, 'reason': 'release test', 'additional_information': 'release test' }] account = account_utils.Account(123, 'vendor') validation_response = action_logic.validate_actions( account, 'TL1234567890', 'Def Jam', '2017-11-12', actions, 123) assert not validation_response assert validation_response.status == 400 assert validation_response.errors['message'] == 'something bad' def test_create_action_success( mocker, patch_datetime_utcnow, account_tuple, feature_engine, get_conflicts_territories_mock, bulk_remove_territories_mock, bulk_create_fingerprint_rules_mock, get_rules_mock): """Expect actions to be created despite an exception in delete_conflict.""" request_payload = { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567890', 'tuid': 123, 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12', 'es_id': 'esid1234567', 'release_action': { 'conflict_ids': [1, 2], 'reason': 'release test', 'additional_information': 'release test' }, 'assert_action': { 'conflict_ids': [3, 4], 'reason': 'assert test', 'additional_information': 'assert test' } } expected_date = datetime.datetime.utcnow().strftime( db_consts.SNOWFLAKE_DATE_FORMAT) expected_actions = [ { 'conflict_id': 1, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }, { 'conflict_id': 2, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }, { 'conflict_id': 3, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }, { 'conflict_id': 4, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }] conflict_ids_return_value = api_utils.make_pagination_response( [1, 2, 3, 4]) mocker.patch.object( action_logic.fact_conflict, 'get_conflict_ids_for_account_and_isrc', return_value=conflict_ids_return_value) mock_action_model = mocker.patch.object( action_logic.action_model, 'create', return_value=response.Response()) mock_delete_action = mocker.patch.object( action_logic.fact_conflict_elasticsearch, 'delete_conflict', side_effect=Exception('error')) result = action_logic.create_actions( account_tuple, request_payload, TEST_USER_ID) assert result mock_action_model.assert_called_with(expected_actions) get_conflicts_territories_mock.assert_called_with([1, 2]) bulk_remove_territories_mock.assert_called() bulk_create_fingerprint_rules_mock.assert_called() get_rules_mock.assert_called() mock_delete_action.assert_called_with( account_utils.Account(type='vendor', id='7123'), ['esid1234567']) def test_create_action_success_with_filtering( mocker, patch_datetime_utcnow, account_tuple, feature_engine, get_conflicts_territories_mock, bulk_remove_territories_mock, bulk_create_fingerprint_rules_mock, get_rules_mock): """Expect actions to be created.""" request_payload = { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567890', 'tuid': 123, 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12', 'es_id': 'esid1234567', 'release_action': { 'conflict_ids': [1, 2], 'reason': 'release test', 'additional_information': 'release test' }, 'assert_action': { 'conflict_ids': [3, 4], 'reason': 'assert test', 'additional_information': 'assert test' } } expected_date = datetime.datetime.utcnow().strftime( db_consts.SNOWFLAKE_DATE_FORMAT) expected_actions = [ { 'conflict_id': 1, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }, { 'conflict_id': 2, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }, { 'conflict_id': 3, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }, { 'conflict_id': 4, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }] mock_delete_action = mocker.patch.object( action_logic.fact_conflict_elasticsearch, 'delete_conflict', return_value=response.Response(status=200)) conflict_ids_return_value = api_utils.make_pagination_response( [1, 2, 3, 4]) mocker.patch.object( action_logic.fact_conflict, 'get_conflict_ids_for_account_and_isrc', return_value=conflict_ids_return_value) mock_action_model = mocker.patch.object( action_logic.action_model, 'create', return_value=response.Response()) result = action_logic.create_actions( account_tuple, request_payload, TEST_USER_ID) assert result mock_action_model.assert_called_with(expected_actions) mock_delete_action.assert_called_with( account_utils.Account(type='vendor', id='7123'), ['esid1234567']) get_conflicts_territories_mock.assert_called_with([1, 2]) bulk_remove_territories_mock.assert_called() bulk_create_fingerprint_rules_mock.assert_called() get_rules_mock.assert_called() def test_create_action_validation_error(mocker, patch_datetime_utcnow, feature_engine): """Expect error response because of failed validation.""" request_payload = { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567890', 'tuid': 123, 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12', 'release_action': { 'conflict_ids': [1, 2], 'reason': 'release test', 'additional_information': 'release test' }, 'assert_action': { 'conflict_ids': [3, 4], 'reason': 'assert test', 'additional_information': 'assert test' } } account = account_utils.Account(123, 'vendor') conflict_ids_return_value = api_utils.make_pagination_response( [1, 2]) mocker.patch.object( action_logic.fact_conflict, 'get_conflict_ids_for_account_and_isrc', return_value=conflict_ids_return_value) mocker.patch.object( action_logic.fact_conflict_elasticsearch, 'delete_conflict', return_value=response.Response(status=200)) with app.app_context(), patch('conflict_manager.logic.action.g') as mock_g: result = action_logic.create_actions( account, request_payload, TEST_USER_ID) assert not result assert result.errors['message'] == error_consts.INVALID_TERRITORIES_MSG mock_g.log.debug.assert_not_called() def test_bulk_create_action_validation_error(mocker, patch_datetime_utcnow, feature_engine): """Expect error response because of failed validation.""" conflicts_count_for_bulk_actions = [ { 'conflict_ids': '4', 'isrc': 'TL1234567890', 'tuid': '123', 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12' }, { 'conflict_ids': '3', 'isrc': 'TL1234567891', 'tuid': '345', 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12' } ] actions = [ { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567890', 'tuid': 123, 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12', 'release_action': { 'conflict_ids': [1, 2], 'reason': 'release test', 'additional_information': 'release test' }, }, { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567891', 'tuid': 456, 'conflicting_owner': 'Ill Will Not', 'conflict_date': '2017-11-13', 'release_action': { 'conflict_ids': [1, 2], 'reason': 'release test', 'additional_information': 'release test' }, 'assert_action': { 'conflict_ids': [3, 4], 'reason': 'assert test', 'additional_information': 'assert test' } } ] account = account_utils.Account(123, 'vendor') mocker.patch.object( action_logic.fact_conflict, 'get_conflicts_ids_for_bulk_actions', return_value=conflicts_count_for_bulk_actions) result = (action_logic.bulk_create_actions(account, actions, TEST_USER_ID)) assert not result assert result.errors['message'] == error_consts.INVALID_TERRITORIES_MSG def test_bulk_create_action_too_many_actions_in_payload( mocker, patch_datetime_utcnow, feature_engine): """Expect error response because actions list payload is too long. Payload contains more actions than currently exist in the DB for the given params. """ conflicts_count_for_bulk_actions = [ { 'conflict_ids': '4', 'isrc': 'TL1234567890', 'tuid': '123', 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12' }, { 'conflict_ids': '3', 'isrc': 'TL1234567891', 'tuid': '345', 'conflicting_owner': 'Ill Will Not', 'conflict_date': '2017-11-12' } ] actions = [ { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567890', 'tuid': 123, 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12', 'release_action': { 'conflict_ids': [4], 'reason': 'release test', 'additional_information': 'release test' }, }, { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567891', 'tuid': 345, 'conflicting_owner': 'Ill Will Not', 'conflict_date': '2017-11-13', 'release_action': { 'conflict_ids': [3], 'reason': 'release test', 'additional_information': 'release test' }, 'assert_action': { 'conflict_ids': [3], 'reason': 'assert test', 'additional_information': 'assert test' } }, { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567892', 'tuid': 567, 'conflicting_owner': 'Ill Will Not', 'conflict_date': '2017-11-13', 'release_action': { 'conflict_ids': [5, 6], 'reason': 'release test', 'additional_information': 'release test' }, 'assert_action': { 'conflict_ids': [7, 8], 'reason': 'assert test', 'additional_information': 'assert test' } } ] account = account_utils.Account(123, 'vendor') mocker.patch.object( action_logic.fact_conflict, 'get_conflicts_ids_for_bulk_actions', return_value=conflicts_count_for_bulk_actions) result = (action_logic.bulk_create_actions(account, actions, TEST_USER_ID)) assert not result assert result.errors['message'] == \ error_consts.BULK_ACTION_PAYLOAD_IS_TOO_LARGE def test_bulk_create_action_success( mocker, patch_datetime_utcnow, account_tuple, feature_engine, get_conflicts_territories_mock, bulk_remove_territories_mock, bulk_create_fingerprint_rules_mock, get_rules_mock): """Expect actions to be created despite an exception in delete_conflict.""" conflicts_count_for_bulk_actions = [ { 'conflict_ids': '2,1', 'isrc': 'TL1234567890', 'tuid': '123', 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12' }, { 'conflict_ids': '1,2', 'isrc': 'TL1234567891', 'tuid': '345', 'conflicting_owner': 'Ill Will Not', 'conflict_date': '2017-11-12' } ] request_payload = [ { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567890', 'tuid': 123, 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12', 'es_id': 'esid1234567', 'release_action': { 'conflict_ids': [1, 2], 'reason': 'release test', 'additional_information': 'release test' }, }, { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567891', 'tuid': 345, 'conflicting_owner': 'Ill Will Not', 'conflict_date': '2017-11-12', 'es_id': 'esid7654321', 'assert_action': { 'conflict_ids': [1, 2], 'reason': 'assert test', 'additional_information': 'assert test' } } ] expected_date = datetime.datetime.utcnow().strftime( db_consts.SNOWFLAKE_DATE_FORMAT) expected_actions = [ { 'conflict_id': 1, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }, { 'conflict_id': 2, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }, { 'conflict_id': 1, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid7654321', 'tuid': 345 }, { 'conflict_id': 2, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid7654321', 'tuid': 345 }, ] mocker.patch.object( action_logic.fact_conflict, 'get_conflicts_ids_for_bulk_actions', return_value=conflicts_count_for_bulk_actions) create_response = [{db_consts.NUMBER_OF_ROWS_INSERTED: 4}] mock_action_model = mocker.patch.object( action_logic.action_model, 'create', return_value=response.Response(create_response)) mock_delete_action = mocker.patch.object( action_logic.fact_conflict_elasticsearch, 'delete_conflict', side_effect=Exception('error')) with app.app_context(), patch('conflict_manager.logic.action.g') as mock_g: result = action_logic.bulk_create_actions( account_tuple, request_payload, TEST_USER_ID) assert result.message == {db_consts.NUMBER_OF_ROWS_INSERTED: 4} mock_action_model.assert_called_with(expected_actions) get_conflicts_territories_mock.assert_called_with([1, 2]) bulk_remove_territories_mock.assert_called() bulk_create_fingerprint_rules_mock.assert_called() get_rules_mock.assert_called() mock_delete_action.assert_called_with( account_utils.Account(type='vendor', id='7123'), ['esid1234567', 'esid7654321']) mock_g.log.debug.assert_called() def test_bulk_create_action_success_with_filtering( mocker, patch_datetime_utcnow, account_tuple, feature_engine, get_conflicts_territories_mock, bulk_remove_territories_mock, bulk_create_fingerprint_rules_mock, get_rules_mock): """Expect actions to be created.""" conflicts_ids_for_bulk_actions = [ { 'conflict_ids': '1,2', 'isrc': 'TL1234567890', 'tuid': '123', 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12', }, { 'conflict_ids': '1,2', 'isrc': 'TL1234567891', 'tuid': '345', 'conflicting_owner': 'Ill Will Not', 'conflict_date': '2017-11-12', } ] request_payload = [ { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567890', 'tuid': 123, 'conflicting_owner': 'Ill Will', 'conflict_date': '2017-11-12', 'es_id': 'esid1234567', 'release_action': { 'conflict_ids': [1, 2], 'reason': 'release test', 'additional_information': 'release test' }, }, { 'territory_standard': 'ISO_3166_2106', 'isrc': 'TL1234567891', 'tuid': 345, 'conflicting_owner': 'Ill Will Not', 'conflict_date': '2017-11-12', 'es_id': 'esid7654321', 'assert_action': { 'conflict_ids': [1, 2], 'reason': 'assert test', 'additional_information': 'assert test' } } ] expected_date = datetime.datetime.utcnow().strftime( db_consts.SNOWFLAKE_DATE_FORMAT) expected_actions = [ { 'conflict_id': 1, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }, { 'conflict_id': 2, 'action': 'release', 'action_date': expected_date, 'reason': 'release test', 'additional_information': 'release test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid1234567', 'tuid': 123 }, { 'conflict_id': 1, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid7654321', 'tuid': 345 }, { 'conflict_id': 2, 'action': 'assert', 'action_date': expected_date, 'reason': 'assert test', 'additional_information': 'assert test', 'account_id': '7123', 'account_type': 'vendor', 'es_id': 'esid7654321', 'tuid': 345 }, ] mock_delete_action = mocker.patch.object( action_logic.fact_conflict_elasticsearch, 'delete_conflict', return_value=response.Response(status=200)) mocker.patch.object( action_logic.fact_conflict, 'get_conflicts_ids_for_bulk_actions', return_value=conflicts_ids_for_bulk_actions) create_response = [{db_consts.NUMBER_OF_ROWS_INSERTED: 4}] mock_action_model = mocker.patch.object( action_logic.action_model, 'create', return_value=response.Response(create_response)) result = action_logic.bulk_create_actions( account_tuple, request_payload, TEST_USER_ID) assert result.message == {db_consts.NUMBER_OF_ROWS_INSERTED: 4} mock_action_model.assert_called_with(expected_actions) mock_delete_action.assert_called_with( account_utils.Account(type='vendor', id='7123'), ['esid1234567', 'esid7654321']) get_conflicts_territories_mock.assert_called_with([1, 2]) bulk_remove_territories_mock.assert_called() bulk_create_fingerprint_rules_mock.assert_called() get_rules_mock.assert_called() def test_validate_bulk_actions(mocker): """Test validate_bulk_actions.""" payload_actions = [ { 'territory_standard': 'ISO_3166_1_2016', 'conflicting_owner': 'WMG', 'conflict_date': '2023-12-02', 'isrc': 'ITU740900001', 'tuid': 3503707, 'es_id': '', 'assert_action': { 'conflict_ids': [ 627751952, 627652630, 627652631 ], 'reason': 'MASTER_COPYRIGHT_OWNER', 'additional_information': 'Something' } }, { 'territory_standard': 'ISO_3166_1_2016', 'conflicting_owner': 'WMG', 'conflict_date': '2023-12-02', 'isrc': 'ITU740900001', 'tuid': 3503707, 'es_id': '', 'assert_action': { 'conflict_ids': [ 627652632 ], 'reason': 'MASTER_COPYRIGHT_OWNER', 'additional_information': 'Something' } } ] existing_conflict_ids = [{ 'conflict_ids': '627751952,627751953,627652630,627652631,627652632,627751951', # noqa: E501 'isrc': 'ITU740900001', 'tuid': 3503707, 'conflicting_owner': 'WMG', 'conflict_date': '2023-12-02' }] mock_get_conflicts_ids_for_bulk_actions = mocker.patch.object( action_logic.fact_conflict, 'get_conflicts_ids_for_bulk_actions', return_value=existing_conflict_ids) action_logic.validate_bulk_actions('', payload_actions) mock_get_conflicts_ids_for_bulk_actions.assert_called() def test_create_carveout_rules_merges_and_deduplicates(mocker): """Test merging and deduplication of carveout rules.""" from conflict_manager.logic.action import _create_carveout_rules # Mock input: two tracks, one with existing carveout, one new territories_to_carveout = [ {'tuid': 1, 'territories': ['US', 'GB']}, {'tuid': 2, 'territories': ['FR']}, ] correlation_id = 'cid-123' # Existing rules: tuid 1 already has a carveout for US existing_rules = [ {'id': 1, 'rules': [ { 'policy': 'carveout', 'service': 'youtube', 'territory': 'US', 'start': None, 'end': None }, { 'policy': 'other', 'service': 'tiktok', 'territory': 'CA', 'start': None, 'end': None }, ]}, {'id': 2, 'rules': []}, ] mock_get_rules = mocker.patch( 'conflict_manager.models.ows_sound_recordings.get_rules', autospec=True) mock_get_rules.return_value.message = existing_rules mock_bulk_create = mocker.patch( 'conflict_manager.models.ows_sound_recordings.bulk_create_fingerprint_rules', # noqa: E501 autospec=True) _create_carveout_rules(territories_to_carveout, correlation_id) # Should merge and deduplicate: tuid 1 gets GB, tuid 2 gets FR expected = { 1: [ { 'policy': 'carveout', 'service': 'youtube', 'territory': 'US', 'start': None, 'end': None }, { 'policy': 'other', 'service': 'tiktok', 'territory': 'CA', 'start': None, 'end': None }, { 'policy': 'carveout', 'service': 'youtube', 'territory': 'GB', 'start': None, 'end': None }, ], 2: [ { 'policy': 'carveout', 'service': 'youtube', 'territory': 'FR', 'start': None, 'end': None }, ], } # Convert keys to int for comparison (function uses int keys) actual = mock_bulk_create.call_args[0][0] assert actual == expected assert mock_bulk_create.call_args[0][1] == correlation_id assert mock_get_rules.called def test_create_carveout_rules_empty_territories(mocker): """Test that empty territories or empty input does not call bulk_create.""" from conflict_manager.logic.action import _create_carveout_rules mock_get_rules = mocker.patch( 'conflict_manager.models.ows_sound_recordings.get_rules', autospec=True) mock_bulk_create = mocker.patch( 'conflict_manager.models.ows_sound_recordings.bulk_create_fingerprint_rules', # noqa: E501 autospec=True) # No track ids _create_carveout_rules([], 'cid-123') assert not mock_get_rules.called assert not mock_bulk_create.called # Territories present but empty _create_carveout_rules([{'tuid': 1, 'territories': []}], 'cid-123') # get_rules is called, but no new rules to add assert mock_get_rules.called # bulk_create should still be called with only existing rules def test_create_carveout_rules_correct_structure(mocker): """Test that the structure sent to bulk_create_fingerprint_rules is correct.""" # noqa: E501 from conflict_manager.logic.action import _create_carveout_rules territories_to_carveout = [ {'tuid': 1, 'territories': ['US']}, ] correlation_id = 'cid-struct' existing_rules = [ {'id': 1, 'rules': []}, ] mock_get_rules = mocker.patch( 'conflict_manager.models.ows_sound_recordings.get_rules', # noqa: E501 autospec=True) mock_get_rules.return_value.message = existing_rules mock_bulk_create = mocker.patch( 'conflict_manager.models.ows_sound_recordings.bulk_create_fingerprint_rules', # noqa: E501 autospec=True) _create_carveout_rules(territories_to_carveout, correlation_id) sent = mock_bulk_create.call_args[0][0] assert 1 in sent assert sent[1][0]['policy'] == 'carveout' assert sent[1][0]['service'] == 'youtube' assert sent[1][0]['territory'] == 'US' assert sent[1][0]['start'] is None assert sent[1][0]['end'] is None def test_create_carveout_rules_handles_existing_rules(mocker): """Test that existing rules from API are preserved and merged.""" from conflict_manager.logic.action import _create_carveout_rules territories_to_carveout = [ {'tuid': 1, 'territories': ['US']}, ] correlation_id = 'cid-existing' existing_rules = [ {'id': 1, 'rules': [ { 'policy': 'other', 'service': 'youtube', 'territory': 'CA', 'start': None, 'end': None }, ]}, ] mock_get_rules = mocker.patch( 'conflict_manager.models.ows_sound_recordings.get_rules', autospec=True) mock_get_rules.return_value.message = existing_rules mock_bulk_create = mocker.patch( 'conflict_manager.models.ows_sound_recordings.bulk_create_fingerprint_rules', # noqa: E501 autospec=True) _create_carveout_rules(territories_to_carveout, correlation_id) sent = mock_bulk_create.call_args[0][0] assert 1 in sent assert ({'policy': 'other', 'service': 'youtube', 'territory': 'CA', 'start': None, 'end': None} in sent[1]) # noqa: E501 assert ({'policy': 'carveout', 'service': 'youtube', 'territory': 'US', 'start': None, 'end': None} in sent[1]) # noqa: E501 def test_create_carveout_rules_deduplication(mocker): """Test that duplicate carveout rules are not added.""" from conflict_manager.logic.action import _create_carveout_rules territories_to_carveout = [ {'tuid': 1, 'territories': ['US', 'US']}, ] correlation_id = 'cid-dedupe' existing_rules = [ {'id': 1, 'rules': [ { 'policy': 'carveout', 'service': 'youtube', 'territory': 'US', 'start': None, 'end': None }, ]}, ] mock_get_rules = mocker.patch( 'conflict_manager.models.ows_sound_recordings.get_rules', autospec=True) mock_get_rules.return_value.message = existing_rules mock_bulk_create = mocker.patch( 'conflict_manager.models.ows_sound_recordings.bulk_create_fingerprint_rules', # noqa: E501 autospec=True) _create_carveout_rules(territories_to_carveout, correlation_id) sent = mock_bulk_create.call_args[0][0] # Only one carveout rule for US should exist us_rules = [ r for r in sent[1] if r['policy'] == 'carveout' and r['territory'] == 'US' ] assert len(us_rules) == 1