"""Tests for Handlers.""" import json from unittest.mock import call from unittest.mock import MagicMock from unittest.mock import NonCallableMagicMock from unittest.mock import patch import application from oto import response from oto import status as response_code import pytest from backend.constants import error as error_const from backend.handlers import audio_attribute_handlers from backend.handlers import base_handlers from backend.handlers import performer_handlers from backend.handlers import rights_attribute_handlers from backend.handlers import track_handlers from backend.logic import audio_attribute as audio_attribute_logic from backend.logic import isrc as isrc_logic from backend.logic import performer as performer_logic from backend.logic import rights_attribute as rights_attribute_logic from backend.logic import track as track_logic from backend.logic import track_role as track_role_logic from backend.logic import track_spatial as track_spatial_logic from backend.logic import track_vendor as track_vendor_logic # Test Exception Handler @patch('backend.handlers.base_handlers.g') def test_exception_handlerb(mock_g, mock_app): """Verify exception_Handler returns 500 status code and json payload.""" message = ( 'The server encountered an internal error ' 'and was unable to complete your request.') mock_error = MagicMock() with mock_app: server_response = base_handlers.exception_handler(mock_error) mock_g.log.exception.assert_called_with(mock_error) # assert status code is 500 assert server_response.status_code == response_code.INTERNAL_ERROR # assert json payload response_message = json.loads(server_response.data.decode()) assert response_message['message'] == message assert response_message['code'] == response.error.ERROR_CODE_INTERNAL_ERROR @patch('backend.handlers.track_handlers.flask_request') def test_handler_import_product_tracks(flask_request_mock: MagicMock, mocker, test_correlation_id, ): """Test copy_digital_product_tracks handler is successful.""" mocker.patch.object( track_logic, 'copy_tracks_for_product', return_value=response.Response( status=response_code.CREATED)) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( query_string={'orchard_user_id': 'alw:54632'}): res = track_handlers.import_product_tracks(1, 2) assert res.status_code == response_code.CREATED @patch('backend.handlers.track_handlers.flask_request') def test_handler_import_product_tracks_with_body(flask_request_mock: MagicMock, mocker, test_correlation_id): """Test copy_digital_product_tracks handler is successful.""" mocker.patch.object( track_logic, 'copy_tracks_for_product', return_value=response.Response( status=response_code.CREATED)) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( mimetype='application/json', data=json.dumps({'track_list': []}), query_string={'orchard_user_id': 'alw:54632'}, ): res = track_handlers.import_product_tracks(1, 2) assert res.status_code == response_code.CREATED @patch('backend.handlers.performer_handlers.flask_request') def test_get_public_performer_roles(flask_request_mock: MagicMock, mocker): """Test get all performer roles succeed.""" mocker.patch.object( performer_logic, 'get_performer_roles', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context(): res = performer_handlers.get_public_performer_roles() assert res.headers['Cache-Control'] == 'max-age=3600' assert res.status_code == 200 @patch('backend.handlers.track_handlers.flask_request') def test_handler_import_product_tracks_failed(flask_request_mock: MagicMock, mocker, test_correlation_id): """Test copy_digital_product_tracks handler fails.""" mocker.patch.object( track_logic, 'copy_tracks_for_product', return_value=response.create_error_response('test', 'TEST')) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context(): res = track_handlers.import_product_tracks(1, 2) assert res.status_code == response_code.BAD_REQUEST @patch('backend.handlers.track_handlers.flask_request') def test_handler_claim_new_isrc_success(flask_request_mock: MagicMock, mocker): """Test claim_new_isrc handler success.""" mocker.patch.object( track_logic, 'claim_new_isrc', return_value=response.Response( message='US1234567890', status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = True res = track_handlers.claim_new_isrc() assert res.status_code == response_code.OK assert res.data == b'US1234567890' @patch('backend.handlers.track_handlers.flask_request') def test_handler_claim_new_isrc_failure(flask_request_mock: MagicMock, mocker): """Test claim_new_isrc handler failure.""" mocker.patch.object( track_logic, 'claim_new_isrc', return_value=response.Response( message='Unable to claim an ISRC.', status=response_code.INTERNAL_ERROR) ) flask_request_mock.verify_rules_access_standalone.return_value = True res = track_handlers.claim_new_isrc() assert res.status_code == response_code.INTERNAL_ERROR assert res.data == b'Unable to claim an ISRC.' @pytest.mark.parametrize( ( 'test_description', 'query_params', 'request_headers', 'request_context', 'expected_logic_call', 'expected_result', 'expected_exception', ), [ ( 'success, account headers, no query param', {}, { 'Grass-Account-Type': 'vendor', 'Grass-Account-Id': '12321', 'Orchard-User-Id': 'alw:123', }, NonCallableMagicMock(), [call( product_id=445566, account_type='vendor', account_id='12321', user_type='alw', user_id='123', validation_context='pre_submission', profile_type='', identity_id=None )], { 'z_key': 'z value', 'a_key': 'a value', 'e_key': 'e value', }, 'No exceptions were raised in validate_tracks_for_product.', ), ( 'success, account headers, context=pre_submission', { 'validation_context': 'pre_submission' }, { 'Grass-Account-Type': 'vendor', 'Grass-Account-Id': '12321', 'Orchard-User-Id': 'alw:123', }, NonCallableMagicMock(), [call( product_id=445566, account_type='vendor', account_id='12321', user_type='alw', user_id='123', validation_context='pre_submission', profile_type='', identity_id=None )], { 'z_key': 'z value', 'a_key': 'a value', 'e_key': 'e value', }, 'No exceptions were raised in validate_tracks_for_product.', ), ( 'success, label profile headers, no query params', {}, { 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': '112233', 'Orchard-Identity-Id': '8fa71aa1-f04c-4161-8c78-9ffa45b67346', 'Orchard-Roles': 'catalog,sdfsdf', 'Orchard-Requestor-Service': 'graphql-piannone', }, NonCallableMagicMock(profile_type='LabelProfile'), [call( product_id=445566, account_type=None, account_id=None, user_type=None, user_id=None, validation_context='pre_submission', profile_type='', identity_id='8fa71aa1-f04c-4161-8c78-9ffa45b67346' )], { 'z_key': 'z value', 'a_key': 'a value', 'e_key': 'e value', }, 'No exceptions were raised in validate_tracks_for_product.', ), ( 'success, label profile headers, context=pre_submission', { 'validation_context': 'pre_submission' }, { 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': '112233', 'Orchard-Identity-Id': '8fa71aa1-f04c-4161-8c78-9ffa45b67346', 'Orchard-Roles': 'catalog,sdfsdf', 'Orchard-Requestor-Service': 'graphql-piannone', }, NonCallableMagicMock(profile_type='LabelProfile'), [call( product_id=445566, account_type=None, account_id=None, user_type=None, user_id=None, validation_context='pre_submission', profile_type='', identity_id='8fa71aa1-f04c-4161-8c78-9ffa45b67346' )], { 'z_key': 'z value', 'a_key': 'a value', 'e_key': 'e value', }, 'No exceptions were raised in validate_tracks_for_product.', ), ( 'success, content profile headers, no query params', {}, { 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '112233', 'Orchard-Identity-Id': '8fa71aa1-f04c-4161-8c78-9ffa45b67346', 'Orchard-Roles': 'asfsdf', 'Orchard-Requestor-Service': 'graphql-piannone', }, NonCallableMagicMock(profile_type='ContentProfile'), [call( product_id=445566, account_type=None, account_id=None, user_type=None, user_id=None, validation_context='pre_submission', profile_type='', identity_id='8fa71aa1-f04c-4161-8c78-9ffa45b67346' )], { 'z_key': 'z value', 'a_key': 'a value', 'e_key': 'e value', }, 'No exceptions were raised in validate_tracks_for_product.', ), ( 'success, content profile headers, context=pre_submission', { 'validation_context': 'pre_submission' }, { 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '112233', 'Orchard-Identity-Id': '8fa71aa1-f04c-4161-8c78-9ffa45b67346', 'Orchard-Roles': 'asfsdf', 'Orchard-Requestor-Service': 'graphql-piannone', }, NonCallableMagicMock(profile_type='ContentProfile'), [call( product_id=445566, account_type=None, account_id=None, user_type=None, user_id=None, validation_context='pre_submission', profile_type='', identity_id='8fa71aa1-f04c-4161-8c78-9ffa45b67346' )], { 'z_key': 'z value', 'a_key': 'a value', 'e_key': 'e value', }, 'No exceptions were raised in validate_tracks_for_product.', ), ( 'success, content profile headers, context=post_submission', { 'validation_context': 'post_submission' }, { 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '112233', 'Orchard-Identity-Id': '8fa71aa1-f04c-4161-8c78-9ffa45b67346', 'Orchard-Roles': 'asfsdf', 'Orchard-Requestor-Service': 'graphql-piannone', }, NonCallableMagicMock(profile_type='ContentProfile'), [call( product_id=445566, account_type=None, account_id=None, user_type=None, user_id=None, validation_context='post_submission', profile_type='ContentProfile', identity_id='8fa71aa1-f04c-4161-8c78-9ffa45b67346' )], { 'z_key': 'z value', 'a_key': 'a value', 'e_key': 'e value', }, 'No exceptions were raised in validate_tracks_for_product.', ), ( 'success, no headers, no query param', {}, {}, NonCallableMagicMock(), [call( product_id=445566, account_type=None, account_id=None, user_type=None, user_id=None, validation_context='pre_submission', profile_type='', identity_id=None )], { 'z_key': 'z value', 'a_key': 'a value', 'e_key': 'e value', }, 'No exceptions were raised in validate_tracks_for_product.', ), ( 'success, no headers, context=pre_submission', { 'validation_context': 'pre_submission' }, {}, NonCallableMagicMock(profile_type='ContentProfile'), [call( product_id=445566, account_type=None, account_id=None, user_type=None, user_id=None, validation_context='pre_submission', profile_type='', identity_id=None )], { 'z_key': 'z value', 'a_key': 'a value', 'e_key': 'e value', }, 'No exceptions were raised in validate_tracks_for_product.', ), ( 'error, account headers, context=post_submission', { 'validation_context': 'post_submission' }, { 'Grass-Account-Type': 'vendor', 'Grass-Account-Id': '12321', 'Orchard-User-Id': 'alw:123', }, NonCallableMagicMock(), [], 'Exception raised before return.', 'No access to validation context.', ), ( 'error, label profile headers, context=post_submission', { 'validation_context': 'post_submission' }, { 'Orchard-Profile-Type': 'LabelProfile', 'Orchard-Profile-Id': '112233', 'Orchard-Identity-Id': '8fa71aa1-f04c-4161-8c78-9ffa45b67346', 'Orchard-Roles': 'catalog,sdfsdf', 'Orchard-Requestor-Service': 'graphql-piannone', }, NonCallableMagicMock(profile_type='LabelProfile'), [], 'Exception raised before return.', 'No access to validation context.', ), ( 'error, no headers, context=post_submission', { 'validation_context': 'post_submission' }, {}, NonCallableMagicMock(), [], 'Exception raised before return.', 'No access to validation context.', ), ] ) @patch('backend.handlers.track_handlers.flask_request') def test_handler_validate_tracks_for_product( flask_request_mock: MagicMock, mocker, test_description, query_params, request_headers, request_context, expected_logic_call, expected_result, expected_exception ): """Test validate_tracks_for_product handler calls logic.""" flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context() as test_request_context: mocker.patch.object( track_logic, 'validate_tracks_for_product', return_value=response.Response( message={ 'z_key': 'z value', 'a_key': 'a value', 'e_key': 'e value', }, status=response_code.OK, ), ) mock_g = mocker.patch('backend.handlers.track_handlers.g') mock_g.request_context = request_context test_request_context.request.args = query_params test_request_context.request.headers = request_headers result = NonCallableMagicMock(json='Exception raised before return.') with pytest.raises(Exception) as execinfo: result = track_handlers.validate_tracks_for_product(445566) raise Exception( 'No exceptions were raised in validate_tracks_for_product.') assert str(execinfo.value) == expected_exception assert track_logic.validate_tracks_for_product.mock_calls == ( expected_logic_call) assert result.json == expected_result @patch('backend.handlers.track_handlers.flask_request') def test_isrc_match_info(flask_request_mock: MagicMock, mocker): """Test get isrc match info.""" mocker.patch.object( isrc_logic, 'get_tracks_with_isrc_logic', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context(): track_handlers.isrc_match_info('whatever') assert isrc_logic.get_tracks_with_isrc_logic.call_args == call( 'whatever', 10, 0) @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_get_audio_attribute_descriptions(flask_request_mock: MagicMock, mocker): """Test get all audio attribute descriptions succeed.""" mocker.patch.object( audio_attribute_logic, 'get_audio_attributes', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context(): res = audio_attribute_handlers.get_audio_attributes() assert res.status_code == 200 @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_get_audio_attributes_for_track(flask_request_mock: MagicMock, mocker): """Test get audio attributes for specific track.""" mocker.patch.object( audio_attribute_logic, 'get_track_audio_attributes', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context(): audio_attribute_handlers.get_track_audio_attributes(123) assert audio_attribute_logic.get_track_audio_attributes.call_args == call( 123) @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_get_audio_attributes_edits_for_track(flask_request_mock: MagicMock, mocker): """Test get audio attributes edits for specific track.""" mocker.patch.object( audio_attribute_logic, 'get_track_audio_attributes_edits', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context(): audio_attribute_handlers.get_track_audio_attributes_edits(123) assert audio_attribute_logic.get_track_audio_attributes_edits.call_args == call( 123) @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_get_suggested_audio_attributes_for_track(flask_request_mock: MagicMock, mocker): """Test get suggested audio attributes for specific track.""" mocker.patch.object( audio_attribute_logic, 'get_track_suggested_audio_attributes', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context(): audio_attribute_handlers.get_track_suggested_audio_attributes(123) assert audio_attribute_logic.get_track_suggested_audio_attributes.call_args == call( 123) @patch('backend.handlers.rights_attribute_handlers.flask_request') def test_get_rights_attributes_edits_for_track(flask_request_mock: MagicMock, mocker): """Test get rights attributes edits for specific track.""" mocker.patch.object( rights_attribute_logic, 'get_track_rights_attributes_edits', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context(): rights_attribute_handlers.get_track_rights_attributes_edits(123) assert rights_attribute_logic.get_track_rights_attributes_edits.call_args == call( 123) @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_update_track_audio_attributes_success(flask_request_mock: MagicMock, mocker): """Test update audio attributes for specific track success.""" mocker.patch.object( audio_attribute_logic, 'update_track_audio_attributes', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = True valid_headers = { 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '123', 'Orchard-Identity-Id': 'uuid', 'Orchard-Roles': 'view_orchard_sound_recording', 'Orchard-Requestor-Service': 'graphql-test', } with application.app.test_request_context( mimetype='application/json', data=json.dumps( {'items': [{'tuid': 123, 'track_attributes': [1, 2, 3]}]}), headers=valid_headers ): audio_attribute_handlers.upsert_track_audio_attributes() tracks = [{'tuid': 123, 'track_attributes': [1, 2, 3]}] assert audio_attribute_logic.update_track_audio_attributes.call_args == call( tracks, 'uuid') @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_update_track_audio_attributes_failure(flask_request_mock: MagicMock, mocker): """Test update audio attributes for specific track header failure.""" mocker.patch.object( audio_attribute_logic, 'update_track_audio_attributes') flask_request_mock.verify_rules_access_standalone.return_value = True invalid_headers = { 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '123', 'Orchard-Roles': 'view_orchard_sound_recording', 'Orchard-Requestor-Service': 'graphql-test', } with application.app.test_request_context( mimetype='application/json', data=json.dumps( {'items': [{'tuid': 123, 'track_attributes': [1, 2, 3]}]}), headers=invalid_headers ): result = audio_attribute_handlers.upsert_track_audio_attributes() assert not audio_attribute_logic.update_track_audio_attributes.called response_message = json.loads(result.data.decode()) assert response_message['message'] == error_const.ERROR_MESSAGE_MISSING_ORCHARD_IDENTITY_ID @patch('backend.handlers.rights_attribute_handlers.flask_request') def test_update_track_rights_attributes_success(flask_request_mock: MagicMock, mocker): """Test update rights attributes for specific track success.""" mocker.patch.object( rights_attribute_logic, 'update_track_rights_attributes', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = True valid_headers = { 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '123', 'Orchard-Identity-Id': 'uuid', 'Orchard-Roles': 'view_orchard_sound_recording', 'Orchard-Requestor-Service': 'graphql-test', } with application.app.test_request_context( mimetype='application/json', data=json.dumps( {'items': [{'tuid': 123, 'track_attributes': [1, 2, 3]}]}), headers=valid_headers ): rights_attribute_handlers.upsert_track_rights_attributes() tracks = [{'tuid': 123, 'track_attributes': [1, 2, 3]}] assert rights_attribute_logic.update_track_rights_attributes.call_args == call( tracks, 'uuid') @patch('backend.handlers.rights_attribute_handlers.flask_request') def test_update_track_rights_attributes_failure(flask_request_mock: MagicMock, mocker): """Test update rights attributes for specific track header failure.""" mocker.patch.object( rights_attribute_logic, 'update_track_rights_attributes') flask_request_mock.verify_rules_access_standalone.return_value = True invalid_headers = { 'Orchard-Profile-Type': 'ContentProfile', 'Orchard-Profile-Id': '123', 'Orchard-Roles': 'view_orchard_sound_recording', 'Orchard-Requestor-Service': 'graphql-test', } with application.app.test_request_context( mimetype='application/json', data=json.dumps( {'items': [{'tuid': 123, 'track_attributes': [1, 2, 3]}]}), headers=invalid_headers ): result = rights_attribute_handlers.upsert_track_rights_attributes() assert not rights_attribute_logic.update_track_rights_attributes.called response_message = json.loads(result.data.decode()) assert response_message['message'] == error_const.ERROR_MESSAGE_MISSING_ORCHARD_IDENTITY_ID @patch('backend.handlers.track_handlers.flask_request') def test_dataload_validate_tracks_for_vendor_success(flask_request_mock: MagicMock, mocker): """Test dataload_validate_tracks_for_vendor returns valid response.""" mock_response = { "total_products": 1, "products_with_track_issues": 1, "products": [ { "product_id": 1, "track_tuid": 1, "track_isrc": "11111111", "warning_type": "Cross Track ISRC Mismatch", "match_tuid": 2, "match_isrc": "22222222", "match_product_id": 2, "match_upc": "121212121212", "match_vendor_id": 12345 } ] } mocker.patch.object( track_vendor_logic, 'dataload_tracks_isrc_validation', return_value=response.Response(status=200, message=mock_response) ) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( '/vendor/tracks/validate/dataloader', method='POST', data=json.dumps({'product_ids': [1]}), content_type='application/json' ): resp = track_handlers.dataload_validate_tracks_for_vendor() data = json.loads(resp.data) assert resp.status_code == 200 assert data["total_products"] == 1 assert data["products_with_track_issues"] == 1 assert data["products"][0]["product_id"] == 1 @patch('backend.handlers.track_handlers.flask_request') def test_dataload_validate_tracks_for_vendor_failure(flask_request_mock: MagicMock, mocker): """Test dataload_validate_tracks_for_vendor returns valid response.""" flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( '/vendor/tracks/validate/dataloader', method='POST', data=json.dumps({'product_ids': []}), content_type='application/json' ): response = track_handlers.dataload_validate_tracks_for_vendor() assert response.status_code == 400 @patch('backend.handlers.track_handlers.flask_request') def test_handler_import_product_tracks_with_false_access_rule( flask_request_mock: MagicMock, mocker, test_correlation_id, ): """Test copy_digital_product_tracks handler when standalone verify rules access failed.""" copy_tracks_for_product_mock = mocker.patch.object( track_logic, 'copy_tracks_for_product', return_value=response.Response( status=response_code.CREATED)) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context( query_string={'orchard_user_id': 'alw:54632'}): res = track_handlers.import_product_tracks(1, 2) assert res.status_code == 403 copy_tracks_for_product_mock.assert_not_called() @patch('backend.handlers.track_handlers.flask_request') def test_handler_claim_new_isrc_with_false_access_rule(flask_request_mock: MagicMock, mocker): """Test claim_new_isrc handler when standalone verify rules access failed.""" claim_new_isrc_mock = mocker.patch.object( track_logic, 'claim_new_isrc', return_value=response.Response( message='US1234567890', status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = False result = track_handlers.claim_new_isrc() assert result.status_code == 403 claim_new_isrc_mock.assert_not_called() @patch('backend.handlers.track_handlers.flask_request') def test_handler_validate_tracks_for_product_with_false_access_rule( flask_request_mock: MagicMock, mocker, ): """Test validate_tracks_for_product handler when standalone verify rules access failed.""" flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context(): validate_tracks_for_product_mock = mocker.patch.object( track_logic, 'validate_tracks_for_product', ) result = track_handlers.validate_tracks_for_product(445566) assert result.status_code == 403 validate_tracks_for_product_mock.assert_not_called() @patch('backend.handlers.track_handlers.flask_request') def test_isrc_match_info_with_false_access_rule(flask_request_mock: MagicMock, mocker): """Test isrc_match_info handler when standalone verify rules access failed.""" get_tracks_with_isrc_logic_mock = mocker.patch.object( isrc_logic, 'get_tracks_with_isrc_logic', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context(): track_handlers.isrc_match_info('whatever') get_tracks_with_isrc_logic_mock.assert_not_called() @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_get_audio_attribute_descriptions_with_false_access_rule(flask_request_mock: MagicMock, mocker): """Test get_audio_attributes handler when standalone verify rules access failed.""" get_audio_attributes_mock = mocker.patch.object( audio_attribute_logic, 'get_audio_attributes', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context(): result = audio_attribute_handlers.get_audio_attributes() assert result.status_code == 403 get_audio_attributes_mock.assert_not_called() @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_get_audio_attributes_for_track_with_false_access_rule(flask_request_mock: MagicMock, mocker): """Test get_track_audio_attributes handler when standalone verify rules access failed.""" get_track_audio_attributes_mock = mocker.patch.object( audio_attribute_logic, 'get_track_audio_attributes', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context(): result = audio_attribute_handlers.get_track_audio_attributes(123) assert result.status_code == 403 get_track_audio_attributes_mock.assert_not_called() @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_get_audio_attributes_edits_for_track_with_false_access_rule(flask_request_mock: MagicMock, mocker): """Test get_track_audio_attributes_edits handler when standalone verify rules access failed.""" get_track_audio_attributes_edits_mock = mocker.patch.object( audio_attribute_logic, 'get_track_audio_attributes_edits', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context(): audio_attribute_handlers.get_track_audio_attributes_edits(123) get_track_audio_attributes_edits_mock.assert_not_called() @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_get_suggested_audio_attributes_for_track_with_false_access_rule(flask_request_mock: MagicMock, mocker): """Test get_track_suggested_audio_attributes handler when standalone verify rules access failed.""" get_track_suggested_audio_attributes_mock = mocker.patch.object( audio_attribute_logic, 'get_track_suggested_audio_attributes', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context(): audio_attribute_handlers.get_track_suggested_audio_attributes(123) get_track_suggested_audio_attributes_mock.assert_not_called() @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_update_track_audio_attributes_with_false_access_rule(flask_request_mock: MagicMock, mocker): """Test upsert_track_audio_attributes handler when standalone verify rules access failed.""" update_track_audio_attributes_mock = mocker.patch.object( audio_attribute_logic, 'update_track_audio_attributes', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context( mimetype='application/json', data=json.dumps( {'items': [{'tuid': 123, 'track_attributes': [1, 2, 3]}]}), headers={"test": "test_headers"} ): audio_attribute_handlers.upsert_track_audio_attributes() update_track_audio_attributes_mock.assert_not_called() @patch('backend.handlers.rights_attribute_handlers.flask_request') def test_update_track_rights_attributes_with_false_access_rule(flask_request_mock: MagicMock, mocker): """Test upsert_track_rights_attributes handler when standalone verify rules access failed.""" update_track_rights_attributes_mock = mocker.patch.object( rights_attribute_logic, 'update_track_rights_attributes', return_value=response.Response(message=[])) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context( mimetype='application/json', data=json.dumps( {'items': [{'tuid': 123, 'track_attributes': [1, 2, 3]}]}), headers={"test": "test_headers"} ): rights_attribute_handlers.upsert_track_rights_attributes() update_track_rights_attributes_mock.assert_not_called() @patch('backend.handlers.track_handlers.flask_request') def test_dataload_validate_tracks_for_vendor_with_false_access_rule(flask_request_mock: MagicMock, mocker): """Test dataload_validate_tracks_for_vendor handler when standalone verify rules access failed.""" dataload_tracks_isrc_validation_mock = mocker.patch.object( track_vendor_logic, 'dataload_tracks_isrc_validation', return_value=response.Response(status=200, message={"test"}) ) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context( '/vendor/tracks/validate/dataloader', method='POST', data=json.dumps({'product_ids': [1]}), content_type='application/json' ): resp = track_handlers.dataload_validate_tracks_for_vendor() assert resp.status_code == 403 dataload_tracks_isrc_validation_mock.assert_not_called() @patch('backend.handlers.track_handlers.g') @patch('backend.handlers.track_handlers.flask_request') def test_handler_create_track_spatial_header_unauthorized( flask_request_mock: MagicMock, mock_g: MagicMock, mocker, fixture_app ): """Test create_track_spatial handler.""" create_track_spatial_mock = mocker.patch.object( track_spatial_logic, 'create_track_spatial', return_value=response.Response( message='{"isrc": "US1234567890"}', status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = False mock_g.request_context.jwt_identity_id = None with application.app.test_request_context( query_string={'orchard_user_id': 'alw:54632'}): res = track_handlers.create_track_spatial(1) assert res.status_code == 403 create_track_spatial_mock.assert_not_called() @patch('backend.handlers.track_handlers.flask_request') def test_handler_create_track_spatial_headers_authorized( flask_request_mock: MagicMock, mocker, ): """Test create_track_spatial handler.""" create_track_spatial_mock = mocker.patch.object( track_spatial_logic, 'create_track_spatial', return_value=response.Response( message='{"isrc": "US1234567890"}', status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( method='POST', content_type='application/json', data=json.dumps({}), query_string={'orchard_user_id': 'alw:54632'}): res = track_handlers.create_track_spatial(1) assert res.status_code == 200 create_track_spatial_mock.assert_called_once_with( tuid=1, data={}, account_type=None, account_id=None ) @patch('backend.handlers.track_handlers.handlers_util.is_jwt_identity_authorized') @patch('backend.handlers.track_handlers.g') @patch('backend.handlers.track_handlers.flask_request') def test_handler_create_track_spatial_invalid_jwt( flask_request_mock: MagicMock, mock_g: MagicMock, mock_is_jwt_identity_authorized: MagicMock, mocker, fixture_app ): """Test create_track_spatial handler.""" create_track_spatial_mock = mocker.patch.object( track_spatial_logic, 'create_track_spatial', return_value=response.Response( message='{"isrc": "US1234567890"}', status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = False mock_g.request_context.jwt_identity_id = 'dummy_token' mock_is_jwt_identity_authorized.return_value = False with application.app.test_request_context( query_string={'orchard_user_id': 'alw:54632'}): res = track_handlers.create_track_spatial(1) res_data = json.loads(res.data.decode()) assert res.status_code == 403 assert res_data['code'] == error_const.OWS_PERMISSIONS_ERROR_CODE assert res_data['message'] == error_const.OWS_PRODUCT_FORBIDDEN_MESSAGE create_track_spatial_mock.assert_not_called() @patch('backend.handlers.track_handlers.handlers_util.is_jwt_identity_authorized') @patch('backend.handlers.track_handlers.g') @patch('backend.handlers.track_handlers.flask_request') def test_handler_create_track_spatial_valid_jwt( flask_request_mock: MagicMock, mock_g: MagicMock, mock_is_jwt_identity_authorized: MagicMock, mocker, fixture_app ): """Test create_track_spatial handler.""" create_track_spatial_mock = mocker.patch.object( track_spatial_logic, 'create_track_spatial', return_value=response.Response( message='{"isrc": "US1234567890"}', status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = False mock_g.request_context.jwt_identity_id = 'dummy_token' mock_is_jwt_identity_authorized.return_value = True with application.app.test_request_context( method='POST', content_type='application/json', data=json.dumps({}), query_string={'orchard_user_id': 'alw:54632'}): res = track_handlers.create_track_spatial(1) assert res.status_code == 200 create_track_spatial_mock.assert_called_once_with( tuid=1, data={}, account_type=None, account_id=None ) @patch('backend.handlers.track_handlers.flask_request') def test_handler_update_track_spatial(flask_request_mock: MagicMock, mocker): """Test update_track_spatial handler.""" update_track_spatial_mock = mocker.patch.object( track_spatial_logic, 'update_track_spatial', return_value=response.Response( message='{"isrc": "US1234567890"}', status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context( query_string={'orchard_user_id': 'alw:54632'}): res = track_handlers.update_track_spatial(1) assert res.status_code == 403 update_track_spatial_mock.assert_not_called() @patch('backend.handlers.track_handlers.flask_request') def test_handler_delete_track_spatial(flask_request_mock: MagicMock, mocker): """Test delete_track_spatial handler.""" delete_track_spatial_mock = mocker.patch.object( track_spatial_logic, 'delete_track_spatial', return_value=response.Response(status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context( query_string={'orchard_user_id': 'alw:54632'}): res = track_handlers.delete_track_spatial(1) assert res.status_code == 403 delete_track_spatial_mock.assert_not_called() @patch('backend.handlers.track_handlers.flask_request') def test_handler_get_spatial_isrc_map_by_product_id_unauthorized( flask_request_mock: MagicMock, mocker, ): """Test get_spatial_isrc_map_by_product_id returns 403 when unauthorized.""" logic_mock = mocker.patch.object( track_spatial_logic, 'get_spatial_isrc_map_by_product_id', return_value=response.Response(message={}, status=response_code.OK), ) flask_request_mock.verify_rules_access_standalone.return_value = False with application.app.test_request_context(): res = track_handlers.get_spatial_isrc_map_by_product_id(1) assert res.status_code == 403 logic_mock.assert_not_called() @patch('backend.handlers.track_handlers.flask_request') def test_handler_get_spatial_isrc_map_by_product_id( flask_request_mock: MagicMock, mocker, ): """Test get_spatial_isrc_map_by_product_id returns ISRC map on success.""" logic_mock = mocker.patch.object( track_spatial_logic, 'get_spatial_isrc_map_by_product_id', return_value=response.Response(message={100: 'US1234567890'}, status=response_code.OK), ) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( query_string={'orchard_user_id': 'alw:54632'}): res = track_handlers.get_spatial_isrc_map_by_product_id(1) assert res.status_code == 200 logic_mock.assert_called_once_with( product_id=1, account_type=None, account_id=None, ) @patch('backend.handlers.track_handlers.g') @patch('backend.handlers.track_handlers.flask_request') def test_handler_get_track_by_upc_and_isrc_unauthorized( flask_request_mock: MagicMock, mock_g: MagicMock, mocker, fixture_app, ): """Test get_track_by_upc_and_isrc returns 403 when unauthorized.""" get_track_mock = mocker.patch.object( track_logic, 'get_track_by_upc_and_isrc', return_value=response.Response(message={'tuid': 123}, status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = False mock_g.request_context.jwt_identity_id = None with application.app.test_request_context( '/track/upc/123456789012/isrc/US1234567890'): res = track_handlers.get_track_by_upc_and_isrc( upc=123456789012, isrc='US1234567890') assert res.status_code == 403 get_track_mock.assert_not_called() @patch('backend.handlers.track_handlers.g') @patch('backend.handlers.track_handlers.handlers_util.is_jwt_identity_authorized') @patch('backend.handlers.track_handlers.flask_request') def test_handler_get_track_by_upc_and_isrc_invalid_jwt( flask_request_mock: MagicMock, is_jwt_identity_authorized_mock: MagicMock, g_mock: MagicMock, mocker, fixture_app, ): """Test get_track_by_upc_and_isrc returns 403 when JWT is invalid.""" get_track_mock = mocker.patch.object( track_logic, 'get_track_by_upc_and_isrc', return_value=response.Response(message={'tuid': 123}, status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = False g_mock.request_context.jwt_identity_id = 'dummy_token' is_jwt_identity_authorized_mock.return_value = False with application.app.test_request_context( '/track/upc/123456789012/isrc/US1234567890'): res = track_handlers.get_track_by_upc_and_isrc( upc=123456789012, isrc='US1234567890') res_data = json.loads(res.data.decode()) assert res.status_code == 403 assert res_data['code'] == error_const.OWS_PERMISSIONS_ERROR_CODE assert res_data['message'] == error_const.OWS_PRODUCT_FORBIDDEN_MESSAGE get_track_mock.assert_not_called() @patch('backend.handlers.track_handlers.g') @patch('backend.handlers.track_handlers.handlers_util.is_jwt_identity_authorized') @patch('backend.handlers.track_handlers.flask_request') def test_handler_get_track_by_upc_and_isrc_valid_jwt( flask_request_mock: MagicMock, is_jwt_identity_authorized_mock: MagicMock, g_mock: MagicMock, mocker, fixture_app, ): """Test get_track_by_upc_and_isrc returns full track data with valid JWT.""" get_track_mock = mocker.patch.object( track_logic, 'get_track_by_upc_and_isrc', return_value=response.Response(message={'tuid': 123}, status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = False g_mock.request_context.jwt_identity_id = 'dummy_token' is_jwt_identity_authorized_mock.return_value = True with application.app.test_request_context( '/track/upc/123456789012/isrc/US1234567890'): res = track_handlers.get_track_by_upc_and_isrc( upc=123456789012, isrc='US1234567890') assert res.status_code == 200 get_track_mock.assert_called_once_with( upc=123456789012, isrc='US1234567890') @patch('backend.handlers.track_handlers.flask_request') def test_handler_get_track_by_upc_and_isrc_success( flask_request_mock: MagicMock, mocker): """Test get_track_by_upc_and_isrc returns full track data on success.""" get_track_mock = mocker.patch.object( track_logic, 'get_track_by_upc_and_isrc', return_value=response.Response(message={'tuid': 123}, status=response_code.OK) ) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( '/track/upc/123456789012/isrc/US1234567890'): res = track_handlers.get_track_by_upc_and_isrc( upc=123456789012, isrc='US1234567890') assert res.status_code == 200 get_track_mock.assert_called_once_with( upc=123456789012, isrc='US1234567890') @patch('backend.handlers.track_handlers.g') @patch('backend.handlers.track_handlers.handlers_util.is_jwt_identity_authorized') @patch('backend.handlers.track_handlers.flask_request') def test_handler_update_artist_valid_jwt_m2m( flask_request_mock: MagicMock, is_jwt_identity_authorized_mock: MagicMock, g_mock: MagicMock, mocker, fixture_app, ): """Test update_artist accepts authorized JWT-only M2M requests.""" update_artist_mock = mocker.patch.object( track_role_logic, 'update_artist', return_value=response.Response(message=[], status=response_code.OK), ) flask_request_mock.verify_rules_access_standalone.return_value = False g_mock.request_context.jwt_identity_id = 'dummy_token' is_jwt_identity_authorized_mock.return_value = True with application.app.test_request_context( '/product/123/tracks/change_genre', data=json.dumps({'genre_id': 12, 'subgenre_id': 266}), headers={'Content-Type': 'application/json'}, ): res = track_handlers.update_artist(product_id=123) assert res.status_code == response_code.OK update_artist_mock.assert_called_once_with( product_id=123, request_data={'genre_id': 12, 'subgenre_id': 266}, account_type=None, account_id=None, correlation_id=None, orchard_user_id='dummy_token', ) @patch('backend.handlers.track_handlers.g') @patch('backend.handlers.track_handlers.flask_request') def test_handler_update_artist_access_rule_allows_with_jwt_identity( flask_request_mock: MagicMock, g_mock: MagicMock, mocker, fixture_app, ): """Test update_artist uses JWT identity for orchard_user_id when rules access is allowed.""" update_artist_mock = mocker.patch.object( track_role_logic, 'update_artist', return_value=response.Response(message=[], status=response_code.OK), ) flask_request_mock.verify_rules_access_standalone.return_value = True g_mock.request_context.jwt_identity_id = 'dummy_token' with application.app.test_request_context( '/product/123/tracks/change_genre', data=json.dumps({'genre_id': 12, 'subgenre_id': 266}), headers={'Content-Type': 'application/json'}, ): res = track_handlers.update_artist(product_id=123) assert res.status_code == response_code.OK update_artist_mock.assert_called_once_with( product_id=123, request_data={'genre_id': 12, 'subgenre_id': 266}, account_type=None, account_id=None, correlation_id=None, orchard_user_id='dummy_token', ) # Suggested audio attribute suggestion persistence tests AUDIO_SUGGESTIONS = [ {'audio_attribute_id': 1, 'description': 'N/A', 'track_audio_attribute_id': 0, 'reasons': [{'type': 'spoken_word_check'}]}, {'audio_attribute_id': 2, 'description': 'Soundalike', 'track_audio_attribute_id': 0, 'reasons': [{'type': 'keyword_match', 'keyword': 'soundalike', 'on': 'track_name'}]}, ] AUDIO_SUGGESTIONS_RESPONSE = [ {'audio_attribute_id': 1, 'description': 'N/A', 'track_audio_attribute_id': 0}, {'audio_attribute_id': 2, 'description': 'Soundalike', 'track_audio_attribute_id': 0}, ] @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_suggested_audio_attributes_writes_suggestions_when_inputs_valid( flask_request_mock: MagicMock, mocker ): """Test write_audio_attribute_suggestions is called with correct args when all inputs present.""" mocker.patch.object( audio_attribute_logic, 'get_track_suggested_audio_attributes', return_value=response.Response(message={'items': AUDIO_SUGGESTIONS})) write_mock = mocker.patch.object(audio_attribute_logic, 'write_audio_attribute_suggestions') flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( headers={'Orchard-Identity-Id': 'test-uuid'}, query_string={'review_queue_id': '42', 'persist': 'true'}, ): audio_attribute_handlers.get_track_suggested_audio_attributes(123) write_mock.assert_called_once_with( unique_track_id=123, suggestions=[ {'attribute_id': 1, 'reasons': [{'type': 'spoken_word_check'}]}, {'attribute_id': 2, 'reasons': [ {'type': 'keyword_match', 'keyword': 'soundalike', 'on': 'track_name'}, ]}, ], user_uuid='test-uuid', review_queue_id=42, ) @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_suggested_audio_attributes_skips_write_when_persist_not_set( flask_request_mock: MagicMock, mocker ): """Test write not called when persist param is absent, even with valid inputs.""" mocker.patch.object( audio_attribute_logic, 'get_track_suggested_audio_attributes', return_value=response.Response(message={'items': AUDIO_SUGGESTIONS})) write_mock = mocker.patch.object(audio_attribute_logic, 'write_audio_attribute_suggestions') flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( headers={'Orchard-Identity-Id': 'test-uuid'}, query_string={'review_queue_id': '42'}, ): audio_attribute_handlers.get_track_suggested_audio_attributes(123) write_mock.assert_not_called() @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_suggested_audio_attributes_skips_write_when_review_queue_id_missing( flask_request_mock: MagicMock, mocker ): """Test write not called and suggestions still returned when review_queue_id absent.""" mocker.patch.object( audio_attribute_logic, 'get_track_suggested_audio_attributes', return_value=response.Response(message={'items': AUDIO_SUGGESTIONS})) write_mock = mocker.patch.object(audio_attribute_logic, 'write_audio_attribute_suggestions') flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( headers={'Orchard-Identity-Id': 'test-uuid'}, query_string={'persist': 'true'}, ): mocker.patch('backend.utils.handlers.g') server_response = audio_attribute_handlers.get_track_suggested_audio_attributes(123) write_mock.assert_not_called() assert server_response.status_code == response_code.OK response_message = json.loads(server_response.data.decode()) assert response_message['items'] == AUDIO_SUGGESTIONS_RESPONSE @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_suggested_audio_attributes_skips_write_when_user_uuid_missing( flask_request_mock: MagicMock, mocker ): """Test write not called and suggestions still returned when Orchard-Identity-Id absent.""" mocker.patch.object( audio_attribute_logic, 'get_track_suggested_audio_attributes', return_value=response.Response(message={'items': AUDIO_SUGGESTIONS})) write_mock = mocker.patch.object(audio_attribute_logic, 'write_audio_attribute_suggestions') flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( query_string={'review_queue_id': '42', 'persist': 'true'}, ): mocker.patch('backend.utils.handlers.g') server_response = audio_attribute_handlers.get_track_suggested_audio_attributes(123) write_mock.assert_not_called() assert server_response.status_code == response_code.OK response_message = json.loads(server_response.data.decode()) assert response_message['items'] == AUDIO_SUGGESTIONS_RESPONSE @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_suggested_audio_attributes_skips_write_when_suggestions_empty( flask_request_mock: MagicMock, mocker ): """Test write not called when suggestion result is an empty list.""" mocker.patch.object( audio_attribute_logic, 'get_track_suggested_audio_attributes', return_value=response.Response(message={'items': []})) write_mock = mocker.patch.object(audio_attribute_logic, 'write_audio_attribute_suggestions') flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( headers={'Orchard-Identity-Id': 'test-uuid'}, query_string={'review_queue_id': '42', 'persist': 'true'}, ): audio_attribute_handlers.get_track_suggested_audio_attributes(123) write_mock.assert_not_called() @patch('backend.handlers.audio_attribute_handlers.flask_request') def test_suggested_audio_attributes_returns_suggestions_on_write_error( flask_request_mock: MagicMock, mocker ): """Test suggestions are still returned when the write persister raises an exception.""" mocker.patch.object( audio_attribute_logic, 'get_track_suggested_audio_attributes', return_value=response.Response(message={'items': AUDIO_SUGGESTIONS})) mocker.patch.object( audio_attribute_logic, 'write_audio_attribute_suggestions', side_effect=Exception('DB error')) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( headers={'Orchard-Identity-Id': 'test-uuid'}, query_string={'review_queue_id': '42', 'persist': 'true'}, ): mocker.patch('backend.utils.handlers.g') result = audio_attribute_handlers.get_track_suggested_audio_attributes(123) assert result.status_code == 200 response_message = json.loads(result.data.decode()) assert response_message['items'] == AUDIO_SUGGESTIONS_RESPONSE # Suggested rights attribute suggestion persistence tests RIGHTS_SUGGESTIONS = [ {'rights_attribute_id': 3, 'description': 'Various Artists', 'track_rights_attribute_id': 0, 'reasons': [{'type': 'keyword_match', 'keyword': 'various', 'on': 'release_name'}]}, {'rights_attribute_id': 5, 'description': 'Potential non-exclusive', 'track_rights_attribute_id': 0, 'reasons': [{'type': 'vendor_subaccount_match', 'vendor_id': 99, 'subaccount_id': 7}]}, ] RIGHTS_SUGGESTIONS_RESPONSE = [ {'rights_attribute_id': 3, 'description': 'Various Artists', 'track_rights_attribute_id': 0}, {'rights_attribute_id': 5, 'description': 'Potential non-exclusive', 'track_rights_attribute_id': 0}, ] @patch('backend.handlers.rights_attribute_handlers.flask_request') def test_suggested_rights_attributes_writes_suggestions_when_inputs_valid( flask_request_mock: MagicMock, mocker ): """Test write_rights_attribute_suggestions is called with correct args when all inputs present.""" mocker.patch.object( rights_attribute_logic, 'get_suggested_rights_attributes', return_value=response.Response(message={'items': RIGHTS_SUGGESTIONS})) write_mock = mocker.patch.object(rights_attribute_logic, 'write_rights_attribute_suggestions') flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( headers={'Orchard-Identity-Id': 'test-uuid'}, query_string={'review_queue_id': '42', 'persist': 'true'}, ): rights_attribute_handlers.get_suggested_rights_attributes(123) write_mock.assert_called_once_with( unique_track_id=123, suggestions=[ {'attribute_id': 3, 'reasons': [{'type': 'keyword_match', 'keyword': 'various', 'on': 'release_name'}]}, {'attribute_id': 5, 'reasons': [ {'type': 'vendor_subaccount_match', 'vendor_id': 99, 'subaccount_id': 7}, ]}, ], user_uuid='test-uuid', review_queue_id=42, ) @patch('backend.handlers.rights_attribute_handlers.flask_request') def test_suggested_rights_attributes_skips_write_when_persist_not_set( flask_request_mock: MagicMock, mocker ): """Test write not called when persist param is absent, even with valid inputs.""" mocker.patch.object( rights_attribute_logic, 'get_suggested_rights_attributes', return_value=response.Response(message={'items': RIGHTS_SUGGESTIONS})) write_mock = mocker.patch.object(rights_attribute_logic, 'write_rights_attribute_suggestions') flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( headers={'Orchard-Identity-Id': 'test-uuid'}, query_string={'review_queue_id': '42'}, ): rights_attribute_handlers.get_suggested_rights_attributes(123) write_mock.assert_not_called() @patch('backend.handlers.rights_attribute_handlers.flask_request') def test_suggested_rights_attributes_skips_write_when_review_queue_id_missing( flask_request_mock: MagicMock, mocker ): """Test write not called and suggestions still returned when review_queue_id absent.""" mocker.patch.object( rights_attribute_logic, 'get_suggested_rights_attributes', return_value=response.Response(message={'items': RIGHTS_SUGGESTIONS})) write_mock = mocker.patch.object(rights_attribute_logic, 'write_rights_attribute_suggestions') flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( headers={'Orchard-Identity-Id': 'test-uuid'}, query_string={'persist': 'true'}, ): mocker.patch('backend.utils.handlers.g') handler_response = rights_attribute_handlers.get_suggested_rights_attributes(123) write_mock.assert_not_called() assert json.loads(handler_response.data.decode())['items'] == RIGHTS_SUGGESTIONS_RESPONSE @patch('backend.handlers.rights_attribute_handlers.flask_request') def test_suggested_rights_attributes_skips_write_when_user_uuid_missing( flask_request_mock: MagicMock, mocker ): """Test write not called and suggestions still returned when Orchard-Identity-Id absent.""" mocker.patch.object( rights_attribute_logic, 'get_suggested_rights_attributes', return_value=response.Response(message={'items': RIGHTS_SUGGESTIONS})) write_mock = mocker.patch.object(rights_attribute_logic, 'write_rights_attribute_suggestions') flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( query_string={'review_queue_id': '42', 'persist': 'true'}, ): mocker.patch('backend.utils.handlers.g') result = rights_attribute_handlers.get_suggested_rights_attributes(123) write_mock.assert_not_called() assert result.status_code == 200 assert json.loads(result.data.decode())['items'] == RIGHTS_SUGGESTIONS_RESPONSE @patch('backend.handlers.rights_attribute_handlers.flask_request') def test_suggested_rights_attributes_skips_write_when_suggestions_empty( flask_request_mock: MagicMock, mocker ): """Test write not called when suggestion result is an empty list.""" mocker.patch.object( rights_attribute_logic, 'get_suggested_rights_attributes', return_value=response.Response(message={'items': []})) write_mock = mocker.patch.object(rights_attribute_logic, 'write_rights_attribute_suggestions') flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( headers={'Orchard-Identity-Id': 'test-uuid'}, query_string={'review_queue_id': '42', 'persist': 'true'}, ): rights_attribute_handlers.get_suggested_rights_attributes(123) write_mock.assert_not_called() @patch('backend.handlers.rights_attribute_handlers.flask_request') def test_suggested_rights_attributes_returns_suggestions_on_write_error( flask_request_mock: MagicMock, mocker ): """Test suggestions are still returned when the write persister raises an exception.""" mocker.patch.object( rights_attribute_logic, 'get_suggested_rights_attributes', return_value=response.Response(message={'items': RIGHTS_SUGGESTIONS})) mocker.patch.object( rights_attribute_logic, 'write_rights_attribute_suggestions', side_effect=Exception('DB error')) flask_request_mock.verify_rules_access_standalone.return_value = True with application.app.test_request_context( headers={'Orchard-Identity-Id': 'test-uuid'}, query_string={'review_queue_id': '42', 'persist': 'true'}, ): mocker.patch('backend.utils.handlers.g') result = rights_attribute_handlers.get_suggested_rights_attributes(123) assert result.status_code == 200 assert json.loads(result.data.decode())['items'] == RIGHTS_SUGGESTIONS_RESPONSE