"""Tests for ownership_logic module.""" import decimal from unittest.mock import patch import uuid from oto import response import pytest from masters_registry.connectors import dynamodb from masters_registry.constant import error from masters_registry.constant import field_const from masters_registry.constant import opcode_const from masters_registry.logic import ownership from masters_registry.models import ownership as ownership_model from masters_registry.models.sql import track as track_sql from tests.helpers import patches def test_update_ownership_internal_conflicts_audit_logging_no_conflict(mocker): """Test updating ownership information with no conficts.""" isrc = 'UK234567890' tuid = 1234 update_territories = ['UA', 'UK'] existing_territories = {'AZ': [{'tuid': 1234}], 'FR': [{'tuid': 1235}]} correlation_id = str(uuid.uuid4()) user = 'oa:007' update_isrc_item_field = mocker.patch.object( dynamodb, 'update_isrc_item_field') insert_audit_record = mocker.patch.object( ownership_model, 'insert_audit_record') ownership.update_ownership( isrc, tuid, update_territories, existing_territories, correlation_id, user) insert_audit_record.assert_called_once_with( opcode_const.ADD, isrc, update_territories, correlation_id, user, tuid) expected_territories = { 'UA': [{'tuid': 1234}], 'UK': [{'tuid': 1234}], 'AZ': [{'tuid': 1234}], 'FR': [{'tuid': 1235}], } update_isrc_item_field.assert_called_once_with( isrc, 'territories', expected_territories) def test_update_ownership_internal_conflicts_audit_logging(mocker): """Test updating ownership information with a confict created.""" isrc = 'UK234567890' tuid = 1235 update_territories = ['AZ'] existing_territories = {'AZ': [{'tuid': 1234}], 'FR': [{'tuid': 1235}]} correlation_id = str(uuid.uuid4()) user = 'oa:007' update_isrc_item_field = mocker.patch.object( dynamodb, 'update_isrc_item_field') insert_audit_record = mocker.patch.object( ownership_model, 'insert_audit_record') get_tracks_response = response.Response({ 1234: {'vendor_id': 1}, 1235: {'vendor_id': 2}, }) mocker.patch.object( ownership_model, 'get_tracks', return_value=get_tracks_response) ownership.update_ownership( isrc, tuid, update_territories, existing_territories, correlation_id, user) insert_audit_record.assert_any_call( opcode_const.ADD, isrc, update_territories, correlation_id, user, tuid) expected_territories = { 'AZ': [{'tuid': 1234}, {'tuid': 1235}], 'FR': [{'tuid': 1235}], } update_isrc_item_field.assert_called_once_with( isrc, 'territories', expected_territories) created_conflict = { 'conflicting_tuid': [1234, 1235], 'status': 'CONFLICT_CREATED', } insert_audit_record.assert_any_call( opcode=opcode_const.CONFLICT_CREATED, isrc=isrc, territories=update_territories, correlation_id=correlation_id, user=user, tuid=tuid, source=field_const.MANUAL_EDIT_ISRC, conflict=created_conflict) def test_add_tuid_to_territories_old_format(): """Expect to handle old format territory entry. Old format means this structure: {'isrc': "QSAD123', 'territories': {'AF': {'tuid': 123}}} Code should be able to read it and use to create entry of new format: { 'isrc': "QSAD123', 'territories': {'AF': [{'tuid': 123}, {'tuid':456}]} } """ tuid = 123456 territories = ['AF', 'AD'] existing_territories = {'AF': {'tuid': 90876}, 'AD': {'tuid': 90876}} result = ownership.add_tuid_to_territories( tuid, territories, existing_territories) expected_territories_dict = { 'AF': [{'tuid': 90876}, {'tuid': tuid}], 'AD': [{'tuid': 90876}, {'tuid': tuid}] } assert result == expected_territories_dict def test_add_tuid_to_territory_old_format(): """Expect to generate entry for single territory.""" tuid = 123456 existing_entry = {'tuid': 90876} result = ownership._add_tuid_to_territory(tuid, existing_entry) expected_entry = [{'tuid': 90876}, {'tuid': tuid}] assert result == expected_entry def test_add_tuid_to_territory_new_format(): """Expect to generate entry for single territory.""" tuid = 123456 existing_entry = [{'tuid': 90876}] result = ownership._add_tuid_to_territory(tuid, existing_entry) expected_entry = [{'tuid': 90876}, {'tuid': tuid}] assert result == expected_entry def test_add_tuid_to_territory_handles_unknown_format(): """Expect to handle unknown format of territory.""" with pytest.raises(ValueError): ownership._add_tuid_to_territory(12345, {1, 2}) def test_add_tuid_to_territory_does_not_add_duplicates(): """Expect to have no duplicates in territory entry.""" tuid = 90876 existing_entry = [{'tuid': decimal.Decimal('90876')}] result = ownership._add_tuid_to_territory(tuid, existing_entry) expected_entry = [{'tuid': 90876}] assert result == expected_entry def test_add_tuid_to_territories_handles_absent_territory_new_format(): """Expect success when territory was deleted and know added again.""" tuid = 1234 update_territories = ['AD'] existing_territories = {'AF': {'tuid': 90876}} result = ownership.add_tuid_to_territories( tuid, update_territories, existing_territories) expected_territories = {'AF': {'tuid': 90876}, 'AD': [{'tuid': 1234}]} assert result == expected_territories def test_add_tuid_to_territory_does_not_add_duplicates_old_format(): """Expect to have no duplicates in territory entry.""" tuid = 90876 existing_entry = {'tuid': decimal.Decimal('90876')} result = ownership._add_tuid_to_territory(tuid, existing_entry) expected_entry = [{'tuid': decimal.Decimal('90876')}] assert result == expected_entry @patch( 'masters_registry.models.ows_carveouts.get_dms_carveout_for_upc', new=patches.ows_carveouts_get_dms_carveout_for_upc_not_carved_out_patch) @patch( 'masters_registry.models.ownership.get_upc_by_tuid', new=patches.ownership_get_upc_by_tuid_functional_tests_patch) def test_remove_ownership_resolved_conflict_audit_logging(mocker): """Test remove ownership information with resolved conflict.""" isrc = 'US1234567890' tuid = 1235 territories = ['AZ', 'FR'] existing_territories = { 'AZ': [{'tuid': 1234}, {'tuid': 1235}], 'FR': [{'tuid': 1235}]} correlation_id = str(uuid.uuid4()) user = 'oa:007' get_tracks_response = response.Response({ 1234: {'vendor_id': 1}, 1235: {'vendor_id': 2}, }) updated_ownership = response.Response({ 'territories': { 'AZ': [{'tuid': 1234}]} }) mocker.patch.object( ownership_model, 'get_tracks', return_value=get_tracks_response) mocker.patch.object( ownership_model, 'get_ownership', return_value=updated_ownership) update_isrc_item_field = mocker.patch.object( dynamodb, 'update_isrc_item_field') insert_audit_record = mocker.patch.object( ownership_model, 'insert_audit_record') ownership.remove_ownership( isrc, tuid, territories, existing_territories, correlation_id, user) insert_audit_record.assert_any_call( opcode_const.REMOVE, isrc, territories, correlation_id, user, tuid=tuid) expected_territories = { 'AZ': [{'tuid': 1234}] } update_isrc_item_field.assert_called_once_with( isrc, 'territories', expected_territories) resolved_conflict = { 'conflicting_tuid': [], 'status': 'CONFLICT_RESOLVED', 'resolved': 1 } insert_audit_record.assert_any_call( opcode=opcode_const.CONFLICT_RESOLVED, isrc=isrc, territories=territories, correlation_id=correlation_id, user=user, tuid=tuid, source=field_const.MANUAL_EDIT_ISRC, conflict=resolved_conflict) def test_remove_tuid_from_territories_old_format(): """Expect to get dict of territories without given territory.""" tuid = 123456 remove_territories = ['AF'] existing_territories = {'AF': {'tuid': 90876}, 'AD': {'tuid': 90876}} result = ownership.remove_tuid_from_territories( tuid, remove_territories, existing_territories) expected = {'AD': {'tuid': 90876}} assert result == expected def test_remove_tuid_from_territories_new_format(): """Expect to get dict of territories without given territory.""" tuid = 90876 remove_territories = ['AF'] existing_territories = {'AF': [{'tuid': 90876}], 'AD': [{'tuid': 90876}]} result = ownership.remove_tuid_from_territories( tuid, remove_territories, existing_territories) expected = {'AD': [{'tuid': 90876}]} assert result == expected def test_remove_tuid_from_territories_new_format_several_tuids(): """Expect only given tuid to be removed from territory entry.""" tuid = 90876 remove_territories = ['AF'] existing_territories = { 'AF': [{'tuid': 90876}, {'tuid': 12345}], 'AD': [{'tuid': 90876}] } result = ownership.remove_tuid_from_territories( tuid, remove_territories, existing_territories) expected = { 'AF': [{'tuid': 12345}], 'AD': [{'tuid': 90876}] } assert result == expected def test_remove_tuid_from_territories_unknown_format(): """Expect error because entry is in unknown format.""" remove_territories = ['AF'] existing_territories = {'AF': 'invalid value here'} with pytest.raises(ValueError): ownership.remove_tuid_from_territories( 12345, remove_territories, existing_territories) def test_remove_tuid_from_territories_no_territory(): """Expect to handle case when there is no given territory already.""" tuid = 90876 remove_territories = ['AF'] existing_territories = {'AD': [{'tuid': 90876}]} result = ownership.remove_tuid_from_territories( tuid, remove_territories, existing_territories) expected = {'AD': [{'tuid': 90876}]} assert result == expected @patch( 'masters_registry.connectors.dynamodb.client.batch_get_item', new=patches.dynamodb_batch_get_item_response) def test_bulk_get_ownership(): """Expect to get a list of existing ISRCs.""" ownership_response = ownership.bulk_get_ownership(['QA123']) expected_response = patches.dynamodb_batch_get_item_response() expected = expected_response['Responses']['test-masters_active'] assert ownership_response.status == 200 assert ownership_response.message == expected @patch( 'masters_registry.connectors.dynamodb.client.batch_get_item', new=patches.dynamodb_batch_get_item_response) def test_bulk_get_ownership_not_found(): """Expect an error because at least one of ISRCs is not found.""" ownership_response = ownership.bulk_get_ownership(['QA123', 'QA234']) assert ownership_response.status == 404 assert ownership_response.errors['message'] == ['QA234'] @patch( 'masters_registry.connectors.dynamodb.client.batch_get_item', new=patches.dynamodb_batch_get_item_response) def test_bulk_get_ownership_ignore_missing(): """Expect to get a list of the existing ISRCs, ignoring ISRCs not found.""" ownership_response = ownership.bulk_get_ownership(['QA123', 'QA234'], True) expected_response = patches.dynamodb_batch_get_item_response() expected = expected_response['Responses']['test-masters_active'] assert ownership_response.status == 200 assert ownership_response.message == expected def test_get_tuid_isrc_pairs_for_vendor(mock_mr_session_scope): """Expect to get key/value pairs of ISRC/tuid for given vendor.""" isrcs = ['QA123'] mock_return_value = [(12345, 'QA123')] session_execute_mock = mock_mr_session_scope(mock_return_value) result = ownership.get_tuid_isrc_pairs_for_account( isrcs, 'vendor', 1) session_execute_mock.assert_called_with( track_sql.SELECT_TUID_BY_ISRC_AND_VENDOR_ID, {'vendor_id': 1, 'isrc_list': isrcs}) assert result.status == 200 assert result.message == {'QA123': [12345]} def test_get_tuid_isrc_pairs_for_subaccount(mock_mr_session_scope): """Expect to get key/value pairs of ISRC/tuid for given subaccount.""" isrcs = ['QA123'] mock_return_value = [(12345, 'QA123')] session_execute_mock = mock_mr_session_scope(mock_return_value) result = ownership.get_tuid_isrc_pairs_for_account( isrcs, 'subaccount', 1) session_execute_mock.assert_called_with( track_sql.SELECT_TUID_BY_ISRC_AND_SUBACCOUNT_ID, {'subaccount_id': 1, 'isrc_list': isrcs}) assert result.status == 200 assert result.message == {'QA123': [12345]} def test_get_tuid_isrc_pairs_does_not_belong_to_vendor(mock_mr_session_scope): """Expect error because one of ISRCs doesn't belong to vendor.""" isrcs = ['QA123', 'QA234'] mock_return_value = [(12345, 'QA123')] session_execute_mock = mock_mr_session_scope(mock_return_value) result = ownership.get_tuid_isrc_pairs_for_account( isrcs, 'vendor', 1) session_execute_mock.assert_called_with( track_sql.SELECT_TUID_BY_ISRC_AND_VENDOR_ID, {'vendor_id': 1, 'isrc_list': isrcs}) assert result.status == 403 assert result.errors['code'] == error.ISRC_NOT_OWNED_BY_ACCOUNT assert result.errors['message'] == ['QA234'] def test_get_tuid_isrc_pairs_does_not_belong_to_subaccount( mock_mr_session_scope): """Expect error because one of ISRCs doesn't belong to subaccount.""" isrcs = ['QA123', 'QA234'] mock_return_value = [(12345, 'QA123')] session_execute_mock = mock_mr_session_scope(mock_return_value) result = ownership.get_tuid_isrc_pairs_for_account( isrcs, 'subaccount', 1) session_execute_mock.assert_called_with( track_sql.SELECT_TUID_BY_ISRC_AND_SUBACCOUNT_ID, {'subaccount_id': 1, 'isrc_list': isrcs}) assert result.status == 403 assert result.errors['code'] == error.ISRC_NOT_OWNED_BY_ACCOUNT assert result.errors['message'] == ['QA234'] def test_get_isrcs_from_track_table(mock_mr_session_scope): """Expect to get list of ISRCs.""" isrcs = {'QA123', 'QA234'} session_execute_mock = mock_mr_session_scope([('QA123',), ('QA234',)]) result = ownership.get_isrcs_from_track_table(isrcs) assert result.status == 200 assert result.message == isrcs session_execute_mock.assert_called_with( track_sql.SELECT_ISRCS, {'isrcs': isrcs}) def test_get_isrcs_from_track_table_not_found(mock_mr_session_scope): """Expect error because one of ISRCs wasn't found.""" isrcs = {'QA123', 'QA234'} session_execute_mock = mock_mr_session_scope([('QA123',)]) result = ownership.get_isrcs_from_track_table(isrcs) assert result.status == 404 assert result.errors['code'] == error.NOT_EXISTING_ISRCS_IN_TRACK assert result.errors['message'] == ['QA234'] session_execute_mock.assert_called_with( track_sql.SELECT_ISRCS, {'isrcs': isrcs}) def test_get_tuids_from_territory_old_format(): """Expect to get list of tuids of territory in old format.""" territories = {'AF': {'tuid': 123}} territory_data = territories['AF'] result = ownership._get_tuids_from_territory(territory_data) assert result == [123] def test_get_tuids_from_territory_new_format(): """Expect to get list of tuids of territory in new format.""" territories = {'AF': [{'tuid': 123}, {'tuid': 345}]} territory_data = territories['AF'] result = ownership._get_tuids_from_territory(territory_data) assert result == [123, 345] def test_tuids_exist_in_territory(): """Expect success because tuid exists in territory.""" tuids = [123] dynamo_response = patches.dynamodb_batch_get_item_response() isrc_item = dynamo_response['Responses']['test-masters_active'][0] result = ownership.tuids_exist_in_territory(tuids, isrc_item, 'AF') assert result.status == 200 def test_tuids_exist_in_territory_multiple_tuids(): """Expect success because one of tuids exists in territory. This is for case when there are > 1 for ISRC/vendor_id combination. It means that vendor has same track on different albums. """ tuids = [123, 456] dynamo_response = patches.dynamodb_batch_get_item_response() isrc_item = dynamo_response['Responses']['test-masters_active'][0] result = ownership.tuids_exist_in_territory(tuids, isrc_item, 'AF') assert result.status == 200 def test_tuids_exist_in_territory_not_found(): """Expect error because tuid doesn't exist in territory.""" tuids = [456] dynamo_response = patches.dynamodb_batch_get_item_response() isrc_item = dynamo_response['Responses']['test-masters_active'][0] result = ownership.tuids_exist_in_territory(tuids, isrc_item, 'AF') expected_error = error.NO_TUIDS_ON_GIVEN_TERRITORY.format('AF', 'QA123') assert result.status == 404 assert result.errors['message'] == expected_error def test_tuids_exist_in_territory_no_territoy_data(): """Expect error because isrc_item doesn't contains any data about territory.""" tuids = [456] dynamo_response = patches.dynamodb_batch_get_item_response() isrc_item = dynamo_response['Responses']['test-masters_active'][0] result = ownership.tuids_exist_in_territory(tuids, isrc_item, 'QA') expected_error = error.NO_TUIDS_ON_GIVEN_TERRITORY.format('QA', 'QA123') assert result.status == 404 assert result.errors['message'] == expected_error def test_tuids_exist_in_isrc_territories(): """Expect success because tuid exists in all territories.""" tuids = [123] isrc_item = { 'isrc': 'QA123', 'locked_territories': {}, 'territories': { 'AD': [{'tuid': decimal.Decimal('123')}], 'AF': [{'tuid': decimal.Decimal('123')}]}, 'timestamp': decimal.Decimal('1489012800631.612'), 'updated_timestamp': decimal.Decimal('1514980909.436671') } territories = ['AF', 'AD'] result = ownership.tuids_exist_in_isrc_territories( tuids, isrc_item, territories) assert result.status == 200 def test_tuids_exist_in_isrc_territories_error(): """Expect error because tuid doesn't exist in one territory.""" tuids = [123] isrc_item = { 'isrc': 'QA123', 'locked_territories': {}, 'territories': { 'AD': [{'tuid': decimal.Decimal('123')}], 'AF': [{'tuid': decimal.Decimal('456')}]}, 'timestamp': decimal.Decimal('1489012800631.612'), 'updated_timestamp': decimal.Decimal('1514980909.436671') } territories = ['AF', 'AD'] result = ownership.tuids_exist_in_isrc_territories( tuids, isrc_item, territories) expected_error = error.NO_TUIDS_ON_GIVEN_TERRITORY.format('AF', 'QA123') assert result.status == 404 assert result.errors['message'] == expected_error