"""Tests for update_ownership endpoint.""" from decimal import Decimal import json from unittest.mock import Mock from unittest.mock import patch from boto3.dynamodb import conditions from freezegun import freeze_time from moto import mock_aws import boto3 from oto import response import pytest from masters_registry.models import ownership from masters_registry.models import yt_ownership from tests import test_utils from tests.helpers import patches from masters_registry.connectors.dynamodb import client as outside_dynamodb_client dynamodb_resource = boto3.resource("dynamodb") @pytest.fixture def dynamodb_get_item_response_old_format(): """Response containing item from DynamoDB for given ISRC. Note that for brevity most of territories were removed. In real item there will be 246 territories. """ item = { 'Item': { 'territories': {'US': {'tuid': Decimal('11684704')}}, 'locked_territories': {}, 'isrc': 'GBQXA0910579', 'updated_timestamp': Decimal('1514457624.334598'), 'timestamp': Decimal('1489014691557.2031') }, 'ResponseMetadata': {} } return item @pytest.fixture def dynamodb_get_item_response_new_format(): """Response containing item from DynamoDB for given ISRC. Note that for brevity most of territories were removed. In real item there will be 246 territories. """ item = { 'Item': { 'territories': { 'AF': [{'tuid': Decimal('11432391')}] }, 'locked_territories': {}, 'isrc': 'QA123', 'updated_timestamp': Decimal('1514457624.334598'), 'timestamp': Decimal('1489014691557.2031') }, 'ResponseMetadata': {} } return item @pytest.fixture def test_headers(): """Headers required to test this endpoint.""" headers = { 'Orchard-User-Id': '123', 'Correlation-Id': 'test', 'Content-Type': 'application/json' } return headers @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.ows_territories.get_territories', new=patches.ows_territories_get_territories_afghanistan_patch) @patch( 'masters_registry.logic.masters_registry._is_valid_isrc_tuid', new=lambda *args: True) @patch( 'masters_registry.logic.masters_registry.' '_check_conflicts_with_the_same_label', new=lambda *args: response.Response()) @patch( 'masters_registry.models.yt_ownership.send_message', new=Mock()) @patch( 'masters_registry.models.ownership.get_upc_by_tuid', new=patches.ownership_get_upc_by_tuid_functional_tests_patch) @patch( 'masters_registry.models.ownership.get_tracks', new=patches.ownership_get_tracks_only_vendor_patch) @patch( 'masters_registry.utils.get_timestamp_for_dynamo', new=lambda: patches.TEST_TIME_AS_TIMESTAMP) @mock_aws def test_update_ownership_new_format( client, test_headers, feature_engine, test_isrc, setup_masters_active_table, seed_masters_active_table, setup_new_audit_table, dynamodb_get_item_response_new_format): """Expect new tuid to be appended to existing list of tuid dicts. New format means that in DynamoDB masters-active table in single item we store mapping between territories and tuids like this: { 'isrc': 'GBQXA0910579', 'territories': { 'US': [{'tuid': 11684704}], 'UA': [{'tuid': 11684704}, {'tuid': 1235432}], # and 244 more of same dicts, one for each territory } # rest of field skipped } So when we update ownership we append territory dict to list. """ from moto.core import patch_client, patch_resource patch_client(outside_dynamodb_client) patch_resource(dynamodb_resource) setup_masters_active_table(dynamodb_resource.meta.client) seed_masters_active_table( dynamodb_resource.meta.client, item=dynamodb_get_item_response_new_format['Item']) setup_new_audit_table(dynamodb_resource.meta.client) ignore_fields = ['timestamp', 'updated_timestamp'] data = json.dumps({ 'tuid': 11684704, 'territories': ['AF'] }) update_url = '/ownership/isrc/{}/territories'.format(test_isrc) update_response = client.put(update_url, headers=test_headers, data=data) update_payload = json.loads(update_response.data.decode()) expected_update_payload = {'territories': ['AF'], 'tuid': 11684704} assert update_payload == expected_update_payload get_response = ownership.get_ownership(test_isrc) expected_saved_item = { 'isrc': test_isrc, 'locked_territories': {}, 'territories': { 'AF': [ {'tuid': Decimal('11432391')}, {'tuid': Decimal('11684704')}], }} assert test_utils.equal_dicts( get_response.message, expected_saved_item, ignore_fields) created_audit_entry = ownership.new_audit_table.query( KeyConditionExpression=conditions.Key('isrc').eq(test_isrc) )['Items'][0] expected_audit_entry = { 'territories': {'AF': 11684704}, 'isrc': test_isrc, 'opcode': 'ADD', 'correlation_id': 'test', 'user': '123'} assert test_utils.equal_dicts( created_audit_entry, expected_audit_entry, ignore_fields) yt_ownership.send_message.assert_called_with( test_isrc, {'AF'}, 'test') @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.ows_territories.get_territories', new=patches.ows_territories_get_territories_afghanistan_patch) @patch( 'masters_registry.logic.masters_registry._is_valid_isrc_tuid', new=lambda *args: True) @patch( 'masters_registry.logic.masters_registry.' '_check_conflicts_with_the_same_label', new=lambda *args: response.Response()) @patch( 'masters_registry.models.yt_ownership.send_message', new=Mock()) @patch( 'masters_registry.models.ownership.get_upc_by_tuid', new=patches.ownership_get_upc_by_tuid_functional_tests_patch) @patch( 'masters_registry.models.ownership.get_tracks', new=patches.ownership_get_tracks_only_vendor_patch) @patch( 'masters_registry.utils.get_timestamp_for_dynamo', new=Mock(return_value=patches.TEST_TIME_AS_TIMESTAMP)) @freeze_time(patches.TEST_TIME_AS_STR) @mock_aws def test_update_ownership_new_format_is_idempotent( client, test_headers, feature_engine, test_isrc, setup_masters_active_table, seed_masters_active_table, setup_new_audit_table, dynamodb_get_item_response_new_format): """Expect existing tuid not to be appended to list of tuid dicts. Supposed to prevent scenario when user accidentally submits to `update_ownership` twice. """ from moto.core import patch_client, patch_resource patch_client(outside_dynamodb_client) patch_resource(dynamodb_resource) setup_masters_active_table(dynamodb_resource.meta.client) seed_masters_active_table( dynamodb_resource.meta.client, item=dynamodb_get_item_response_new_format['Item']) setup_new_audit_table(dynamodb_resource.meta.client) data = json.dumps({ 'tuid': 11432391, 'territories': ['AF'] }) update_url = '/ownership/isrc/{}/territories'.format(test_isrc) update_response = client.put(update_url, headers=test_headers, data=data) update_payload = json.loads(update_response.data.decode()) expected_update_payload = {'territories': ['AF'], 'tuid': 11432391} assert update_payload == expected_update_payload get_response = ownership.get_ownership(test_isrc) expected_saved_item = { 'isrc': test_isrc, 'locked_territories': {}, 'territories': {'AF': [{'tuid': Decimal('11432391')}]}, 'timestamp': Decimal('1489014691557.2031'), 'updated_timestamp': Decimal(patches.TEST_TIME_AS_TIMESTAMP)} assert get_response.message == expected_saved_item created_audit_entry = ownership.new_audit_table.query( KeyConditionExpression=conditions.Key('isrc').eq(test_isrc) )['Items'][0] expected_audit_entry = { 'territories': {'AF': 11432391}, 'isrc': test_isrc, 'opcode': 'ADD', 'correlation_id': 'test', 'timestamp': Decimal('1512140400000.0'), 'user': '123'} assert created_audit_entry == expected_audit_entry yt_ownership.send_message.assert_called_with( test_isrc, {'AF'}, 'test') @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.ows_territories.get_territories', new=patches.ows_territories_get_territories_afghanistan_patch) @patch( 'masters_registry.logic.masters_registry._is_valid_isrc_tuid', new=lambda *args: True) @patch( 'masters_registry.utils.get_timestamp_for_dynamo', new=Mock(return_value=patches.TEST_TIME_AS_TIMESTAMP)) @patch( 'masters_registry.models.yt_ownership.send_message', new=Mock()) @patch( 'masters_registry.models.ownership.get_upc_by_tuid', new=patches.ownership_get_upc_by_tuid_functional_tests_patch) @patch( 'masters_registry.models.ownership.get_tracks', new=patches.tracks_response_with_conflict_patch) @mock_aws def test_update_ownership_new_format_conflict_created( mocker, client, test_headers, feature_engine, setup_masters_active_table, test_isrc, seed_masters_active_table, setup_new_audit_table, dynamodb_get_item_response_new_format): """Expect new tuid to be appended to existing list of tuid dicts. Expect internal conflict information to be written to audit log. """ from moto.core import patch_client, patch_resource patch_client(outside_dynamodb_client) patch_resource(dynamodb_resource) setup_masters_active_table(dynamodb_resource.meta.client) seed_masters_active_table( dynamodb_resource.meta.client, item=dynamodb_get_item_response_new_format['Item']) setup_new_audit_table(dynamodb_resource.meta.client) ignore_fields = ['timestamp', 'updated_timestamp'] data = json.dumps({ 'tuid': 11684704, 'territories': ['AF'] }) update_url = '/ownership/isrc/{}/territories'.format(test_isrc) update_response = client.put(update_url, headers=test_headers, data=data) expected_update_payload = {'territories': ['AF'], 'tuid': 11684704} assert json.loads(update_response.data.decode()) == expected_update_payload get_response = ownership.get_ownership(test_isrc) expected_saved_item = { 'isrc': test_isrc, 'locked_territories': {}, 'territories': {'AF': [ {'tuid': Decimal('11432391')}, {'tuid': Decimal('11684704')} ]} } assert test_utils.equal_dicts( get_response.message, expected_saved_item, ignore_fields) created_audit_entries = ownership.new_audit_table.query( KeyConditionExpression=conditions.Key('isrc').eq(test_isrc) )['Items'] assert len(created_audit_entries) == 2 add_audit_item = created_audit_entries[0] conflict_audit_item = created_audit_entries[1] expected_audit_entry = { 'territories': {'AF': Decimal('11684704')}, 'isrc': test_isrc, 'opcode': 'ADD', 'correlation_id': 'test', 'user': '123' } expected_conflict_entry = { 'territories': {'AF': Decimal('11684704')}, 'isrc': test_isrc, 'opcode': 'CONFLICT_CREATED', 'source': 'MANUAL_EDIT_ISRC', 'correlation_id': 'test', 'user': '123', 'conflict': { 'conflicting_tuid': [Decimal('11684704'), Decimal('11432391')], 'status': 'CONFLICT_CREATED' } } assert test_utils.equal_dicts( expected_audit_entry, add_audit_item, ignore_fields) assert test_utils.equal_dicts( expected_conflict_entry, conflict_audit_item, ignore_fields ) yt_ownership.send_message.assert_called_with( test_isrc, {'AF'}, 'test')