"""Test Blacklist Words Logic.""" from unittest.mock import MagicMock from unittest.mock import Mock from owsrequest import request as requests from owsresponse import response import pytest from blacklist_manager import config from blacklist_manager.connectors import mysql from blacklist_manager.connectors import s3 from blacklist_manager.constants import error from blacklist_manager.logic import blacklist_words from blacklist_manager.models import blacklist_reasons \ as blacklist_reasons_model from blacklist_manager.models import blacklist_words \ as blacklist_words_model from blacklist_manager.models import ows_product_workflow from blacklist_manager.models import persister from blacklist_manager.models import features from tests.unit.conftest import valid_blacklist_word_data, \ valid_blacklist_word_data2 # the regex here is to exact match a word that is perceded by a ", " or "\n" followed by a ", " # this is the way the lists of artists are formatted by the ealier retrieval logic @pytest.mark.parametrize( ('word', 'text', 'expected_result'), [ ( #should match nothing 'James', '\nJames D, \nPerson A, \nPerson AB, \n', [] ), ( #should only exact match the first "item" arist_name 'James D', '\nJames D, \nPerson A, \nPerson AB, \n', ['James D, '] ), ( #should only exact match the first and second "items" arist_name, and artist_names 'James D', '\nJames D, \nPerson A, James D, \nPerson AB, \n', ['James D, ', 'Person A, James D, '] ), ( #should exact match all three "items" artist_name, artist_names, and track_artist_names 'James D', '\nJames D, \nPerson A, James D, \nPerson AB, James D, \n', ['James D, ', 'Person A, James D, ', 'Person AB, James D, '] ), ( #should only exact match the secont "item" arist_names 'Person A', '\nJames D, \nPerson A, \nPerson AB, James D, \n', ['Person A, ',] ), ( #should match nothing 'James', '\nJames D, \nPerson A, James D, \nPerson AB, James D, \n', [] ), ( #should exact the last "item" track_artist_names 'Person AB', '\nJames D, \nPerson A, James D, \nPerson AB, \n', ['Person AB, '] ), ( #should match nothing 'AB', '\nJames D, \nPerson A, James D, \nPerson AB, James D, \n', [] ), ( #should match nothing 'D', '\nJames D, \nPerson A, James D, \nPerson AB, James D, \n', [] ), ] ) def test__find_all_artist_exact_regex(word, text, expected_result): result = blacklist_words._find_all_artist_exact_regex(word, text) assert result == expected_result @pytest.mark.parametrize( ('term', 'expected_response'), [ ( None, [ valid_blacklist_word_data.__name__, valid_blacklist_word_data2.__name__, ], ), ( 'Word', [ valid_blacklist_word_data.__name__, ], ), ] ) def test_get_blacklist_words_success( monkeypatch, valid_blacklist_words_response, expected_response, valid_blacklist_words_response_with_count, term): """Test successful retrieval of blacklist words.""" monkeypatch.setattr( blacklist_words_model, 'get_blacklist_words', value=MagicMock( return_value=response.Response( message=valid_blacklist_words_response(expected_response)))) monkeypatch.setattr( blacklist_words_model, 'get_blacklist_words_count', value=MagicMock( return_value=response.Response( message=len(expected_response)))) result = blacklist_words.get_blacklist_words(term=term) assert result.message == valid_blacklist_words_response_with_count( expected_response) assert result.status == 200 @pytest.mark.parametrize('term', [None, 'Word']) def test_blacklist_words_failure( monkeypatch, valid_blacklist_reasons_response, term): """Test failure in retrieval of blacklist words.""" mock_response = Mock() mock_response.status_code = 500 monkeypatch.setattr(requests, 'get', MagicMock( return_value=mock_response)) result = blacklist_words.get_blacklist_words(term=term) assert result.status == 500 @pytest.mark.parametrize(( 'model_response', 'upload_response', 'url_response', 'status', 'expected_err' ), [ ( response.create_not_found_response({'model': 'failed'}), response.Response(), response.Response('test_url'), 404, {'model': 'failed'} ), ( response.Response({'foo': 'bar'}), response.create_not_found_response({'upload': 'failed'}), response.Response('test_url'), 404, {'upload': 'failed'} ), ( response.Response({'foo': 'bar'}), response.Response({'upload': 'success'}), response.create_fatal_response({'sign': 'failed'}), 500, {'sign': 'failed'} ) ]) def test_export_blacklist_words_failure( monkeypatch, model_response, upload_response, url_response, status, expected_err): """Test export_blacklist_words when some calls fail.""" monkeypatch.setattr( blacklist_words_model, 'write_blacklist_words_to_file', value=MagicMock(return_value=model_response)) monkeypatch.setattr(s3, 'upload_file_from_disk', value=MagicMock( return_value=upload_response)) monkeypatch.setattr(s3, 'get_signed_url', value=MagicMock( return_value=url_response)) result = blacklist_words.export_blacklist_words() assert result.status == status assert result.errors['message'] == expected_err def test_export_blacklist_words_success(monkeypatch): """Test export_blacklist_words when all calls pass.""" monkeypatch.setattr( blacklist_words_model, 'write_blacklist_words_to_file', value=MagicMock(return_value=response.Response({'foo': 'bar'}))) monkeypatch.setattr(s3, 'upload_file_from_disk', value=MagicMock( return_value=response.Response())) monkeypatch.setattr(s3, 'get_signed_url', value=MagicMock( return_value=response.Response('test_url'))) result = blacklist_words.export_blacklist_words() assert result assert result.message == {'foo': 'bar', 'download_url': 'test_url'} s3.upload_file_from_disk.assert_called_once() expected_s3_key = '{}/blacklist_words'.format(config.EXPORT_FOLDER_NAME) assert expected_s3_key in s3.upload_file_from_disk.call_args[0][0] def test_delete_blacklist_word_success(monkeypatch): """Test successful deletion of blacklist word.""" blacklist_id = 10 monkeypatch.setattr( blacklist_words_model, 'delete_blacklist_word', value=MagicMock(return_value=response.Response(status=200))) result = blacklist_words.delete_blacklist_word(blacklist_id) assert result.status == 200 def test_delete_blacklist_word_by_id_not_found_error( monkeypatch): """Test failure in deletion of blacklist word.""" blacklist_id = 9 monkeypatch.setattr( blacklist_words_model, 'delete_blacklist_word', value=MagicMock(return_value=response.create_not_found_response( message='Blacklist Id not found.'))) result = blacklist_words.delete_blacklist_word(blacklist_id) assert result.status == 404 def test_create_blacklist_word_success( monkeypatch, blacklist_word_response, blacklist_word_data): """Test successful creation of blacklist word.""" monkeypatch.setattr( blacklist_words_model, 'create_blacklist_word', value=MagicMock( return_value=response.Response( message=blacklist_word_response, status=201))) result = blacklist_words.create_blacklist_word(blacklist_word_data) assert result.message == blacklist_word_response assert result.status == 201 def test_create_blacklist_word_failure( monkeypatch, blacklist_error_word_response, blacklist_error_word_data): """Test failure in creating of blacklist word.""" monkeypatch.setattr( blacklist_words_model, 'create_blacklist_word', value=MagicMock( return_value=response.Response( message=blacklist_error_word_response))) result = blacklist_words.create_blacklist_word( blacklist_error_word_data) assert result.status == 500 @pytest.mark.parametrize( 'blacklist_data', [ { 'word': '=HYPERLINK() Some blacklisted word', 'notes': 'Some notes for given blacklisted word', 'blacklist_reason_id': 3, 'added_by': 562 }, { 'word': 'Some blacklisted word', 'notes': 'Some =SUM() notes for given blacklisted word', 'blacklist_reason_id': 3, 'added_by': 562 } ] ) def test_create_blacklist_word_failure_with_invalid_data(blacklist_data): """Test failure in creating of blacklist word.""" result = blacklist_words.create_blacklist_word( blacklist_data) assert result.errors['message'] == 'Formulas are not allowed in blacklist word data.' assert result.status == 500 def test_update_blacklist_word_success( monkeypatch, blacklist_word_response, blacklist_update_word_data): """Test successful update of blacklist word.""" blacklist_id = 3 monkeypatch.setattr( blacklist_words_model, 'update_blacklist_word', value=MagicMock( return_value=response.Response( message=blacklist_word_response, status=200))) result = blacklist_words.update_blacklist_word( blacklist_id, blacklist_update_word_data) assert result.message == blacklist_word_response assert result.status == 200 def test_update_blacklist_word_failure( monkeypatch, blacklist_error_word_response, blacklist_error_word_data): """Test failure in update of blacklist word.""" blacklist_id = 1 monkeypatch.setattr( blacklist_words_model, 'update_blacklist_word', value=MagicMock( return_value=response.Response( message=blacklist_error_word_response, status=500))) result = blacklist_words.update_blacklist_word( blacklist_id, blacklist_error_word_data) assert result.status == 500 def test_update_blacklist_word_by_id_not_found_error( monkeypatch, blacklist_update_word_data): """Test failure in update of blacklist word.""" blacklist_id = 2 monkeypatch.setattr( blacklist_words_model, 'update_blacklist_word', value=MagicMock(return_value=response.create_not_found_response( message='Blacklist Id not found.'))) result = blacklist_words.update_blacklist_word( blacklist_id, blacklist_update_word_data) assert result.status == 404 @pytest.mark.parametrize( ('blacklist_validation', 'product_blacklisting_status'), [ (True, {'validation_error': True}), (False, None) ]) def test_validate_product_for_blacklisting( monkeypatch, fetched_product_details_data, blacklist_validation, product_blacklisting_status, valid_response_for_release_validation): """Test validation of release against blacklist words.""" monkeypatch.setattr( persister, 'fetch_product_details_by_id', value=MagicMock( return_value=response.Response( message=fetched_product_details_data))) monkeypatch.setattr( blacklist_words_model, 'get_blacklist_words_for_validation', value=MagicMock( return_value=response.Response( message=valid_response_for_release_validation))) monkeypatch.setattr( ows_product_workflow, 'get_error_corrections', value=MagicMock( return_value=response.Response( message={}))) monkeypatch.setattr( blacklist_words, '_is_blacklisted_text', value=MagicMock( return_value=blacklist_validation)) result = blacklist_words.validate_product_for_blacklisting(2) assert result.message == product_blacklisting_status if blacklist_validation: assert result.status == 400 else: assert result.status == 200 @pytest.mark.parametrize( ( 'test_description', 'blacklisted_words_option', 'is_enabled_artist_field_blocklist_exact_match', 'expected_match_fields_result', 'expected_result_status' ), [ ('match on store-blocked artist only eligible text', 0, False, { 'artist_name', 'artist_names', 'track_artist_names' }, 400), ('match on store-blocked artist only exact text', 0, True, { 'artist_name' }, 400), ('match on store-blocked artist only exact text with no matches', 2, True, {}, 200), ('match on store-blocked artist only with single char word edge case no matches', 3, False, {}, 200), ('match on all text', 1, False, { 'release_name', 'vendor_catalog_number', 'format', 'versions', 'artist_names', 'vendor_release_identifier', 'subaccount_name', 'meta_languages', 'label', 'p_lines', 'company', 'track_names', 'track_artist_names', 'vendor_track_identifiers', 'c_line', 'artist_name' }, 400) ]) def test_validate_product_for_blacklisting_matched_fields( mocker, mock_app, monkeypatch, blacklist_words_for_validation_items, blacklist_words_for_validation_items_store_blocked, blacklist_words_for_validation_items_no_matches, blacklist_words_for_validation_items_single_character_edge_case, fetch_product_details_by_id_result, test_description, blacklisted_words_option, is_enabled_artist_field_blocklist_exact_match, expected_match_fields_result, expected_result_status ): """Test validation of release against blacklist words.""" blacklisted_words_options = [ blacklist_words_for_validation_items_store_blocked, blacklist_words_for_validation_items, blacklist_words_for_validation_items_no_matches, blacklist_words_for_validation_items_single_character_edge_case ] mock_blacklisted_words = blacklisted_words_options[blacklisted_words_option] mock_valid_list_of_reasons = { item['reason_id']: {'reason': 'X', 'alert': 'X', 'contact': 'X'} for item in mock_blacklisted_words } mocker.patch.object( features, "verify_feature_eligibility", return_value=is_enabled_artist_field_blocklist_exact_match, ) mock_app.g.request_context.identity_id = "foo" monkeypatch.setattr( persister, 'fetch_product_details_by_id', value=MagicMock( return_value=response.Response( message=fetch_product_details_by_id_result))) monkeypatch.setattr( blacklist_words_model, 'get_blacklist_words_for_validation', value=MagicMock( return_value=response.Response( message={'items': mock_blacklisted_words}))) monkeypatch.setattr( blacklist_reasons_model, 'get_blacklist_reasons_by_id', value=MagicMock( return_value=response.Response( message=mock_valid_list_of_reasons))) monkeypatch.setattr( ows_product_workflow, 'get_error_corrections', value=MagicMock( return_value=response.Response( message={}))) result = blacklist_words.validate_product_for_blacklisting(2) matched_field_result = {} if result.message: validation_error = result.message['validation_error'] # 'Store-Blocked Artist' (reason ID = 1) matches are isolated into # their own result key, so gather matched fields from both. matched_items = ( validation_error['items'] + validation_error.get('store_blocked_artist', []) ) matched_field_result = { item["matched_field"] for item in matched_items } assert matched_field_result == expected_match_fields_result assert result.status == expected_result_status def _store_blocked_artist_validation_text_fixtures(monkeypatch): """Shared blacklist words / reasons setup for the flag tests below.""" blacklist_words_data = { 'items': [ {'word': 'James', 'reason_id': 1}, {'word': 'reina', 'reason_id': 3}, ] } # text_fields map matched values to the product field they came from. text_fields = { 'James': ['artist_name'], 'reina': ['release_name'], } reasons = { 1: {'reason': 'Store-Blocked Artist', 'alert': 'A', 'contact': 'C'}, 3: {'reason': 'Valid Reason', 'alert': 'A', 'contact': 'C'}, } monkeypatch.setattr( blacklist_reasons_model, 'get_blacklist_reasons_by_id', value=MagicMock(return_value=response.Response(message=reasons))) return blacklist_words_data, text_fields def test_is_blacklisted_text_isolates_store_blocked_artist_flag_on( mock_app, mocker, monkeypatch): """With the flag on, reason-1 matches return as their own result.""" # Only the store-blocked-artist validation flag is on; exact-match stays # off so the reason-1 word still matches via PRODUCT_REGEX. mocker.patch.object( features, "verify_feature_eligibility", side_effect=lambda feature_name, attributes: feature_name == "content_store_blocked_artist_validation") mock_app.g.request_context.identity_id = "foo" blacklist_words_data, text_fields = \ _store_blocked_artist_validation_text_fixtures(monkeypatch) full_text = '\n{}\n'.format('\n'.join(text_fields.keys())) result = blacklist_words._is_blacklisted_text( full_text, blacklist_words_data, blacklist_words.PRODUCT_REGEX, None, text_fields) # The reason-1 match is isolated under 'store_blocked_artist'... assert 'store_blocked_artist' in result assert [i['matched_field'] for i in result['store_blocked_artist']] == ['artist_name'] assert all(i['reason'] == 'Store-Blocked Artist' for i in result['store_blocked_artist']) # ...and is kept out of the generic 'items' result. assert [i['matched_field'] for i in result['items']] == ['release_name'] assert all(i['reason'] != 'Store-Blocked Artist' for i in result['items']) def test_is_blacklisted_text_store_blocked_artist_flag_off( mock_app, mocker, monkeypatch): """With the flag off, reason-1 matches stay in the generic result.""" mocker.patch.object( features, "verify_feature_eligibility", return_value=False) mock_app.g.request_context.identity_id = "foo" blacklist_words_data, text_fields = \ _store_blocked_artist_validation_text_fixtures(monkeypatch) full_text = '\n{}\n'.format('\n'.join(text_fields.keys())) result = blacklist_words._is_blacklisted_text( full_text, blacklist_words_data, blacklist_words.PRODUCT_REGEX, None, text_fields) # No separate bucket; the reason-1 match falls back into 'items'. assert 'store_blocked_artist' not in result assert {i['matched_field'] for i in result['items']} == {'artist_name', 'release_name'} assert any(i['reason'] == 'Store-Blocked Artist' for i in result['items']) def test_validate_product_for_blacklisting_error_correction( mock_app, mocker, monkeypatch, fetched_product_details_data, error_correction_data, valid_list_of_reasons_for_release_validation, valid_response_for_release_validation): """Test validation of release against blacklist words.""" mocker.patch.object( features, "verify_feature_eligibility", return_value=False, ) mock_app.g.request_context.identity_id = "foo" monkeypatch.setattr( persister, 'fetch_product_details_by_id', value=MagicMock( return_value=response.Response( message=fetched_product_details_data))) monkeypatch.setattr( blacklist_words_model, 'get_blacklist_words_for_validation', value=MagicMock( return_value=response.Response( message=valid_response_for_release_validation))) monkeypatch.setattr( blacklist_reasons_model, 'get_blacklist_reasons_by_id', value=MagicMock( return_value=response.Response( message=valid_list_of_reasons_for_release_validation))) monkeypatch.setattr( ows_product_workflow, 'get_error_corrections', value=MagicMock( return_value=response.Response( message=error_correction_data))) result = blacklist_words.validate_product_for_blacklisting(2) assert result.message == { 'validation_error': { 'matched_blacklist_words': ['abc'], 'items': [ { 'word': 'abc', 'reason': 'reasonable reason', 'alert': 'abc alert', 'contact': 'asdf@theorchard.com', 'matched_field': 'release_name' } ] } } assert result.status == 400 def test_validate_product_for_blacklisting_error_correction_for_missing_value( mock_app, mocker, monkeypatch, fetched_product_details_data, error_correction_data_with_vendor_release_identifier, valid_list_of_reasons_for_release_validation, valid_response_for_release_validation): """Test validation of release against blacklist words.""" mocker.patch.object( features, "verify_feature_eligibility", return_value=False, ) mock_app.g.request_context.identity_id = "foo" monkeypatch.setattr( persister, 'fetch_product_details_by_id', value=MagicMock( return_value=response.Response( message=fetched_product_details_data))) monkeypatch.setattr( blacklist_words_model, 'get_blacklist_words_for_validation', value=MagicMock( return_value=response.Response( message=valid_response_for_release_validation))) monkeypatch.setattr( blacklist_reasons_model, 'get_blacklist_reasons_by_id', value=MagicMock( return_value=response.Response( message=valid_list_of_reasons_for_release_validation))) monkeypatch.setattr( ows_product_workflow, 'get_error_corrections', value=MagicMock( return_value=response.Response( message=error_correction_data_with_vendor_release_identifier))) result = blacklist_words.validate_product_for_blacklisting(2) assert result.message == { 'validation_error': { 'matched_blacklist_words': ['abc'], 'items': [ { 'word': 'abc', 'reason': 'reasonable reason', 'alert': 'abc alert', 'contact': 'asdf@theorchard.com', 'matched_field': 'release_name' } ] } } assert result.status == 400 def test_validate_product_for_blacklisting_product_not_found(monkeypatch): """Test validate_product_for_blacklisting when product not found.""" product_id = 2 monkeypatch.setattr( persister, 'fetch_product_details_by_id', value=MagicMock( return_value=response.create_not_found_response( message=error.PRODUCT_NOT_FOUND_MESSAGE.format(product_id)))) result = blacklist_words.validate_product_for_blacklisting(product_id) assert result.status == 404 @pytest.mark.parametrize( ('product_data', 'is_blacklisted_response'), [ ('my name is reina test', True), ('This is a test test', False), ]) def test_is_blacklisted_text_standard_regex( mock_app, mocker, monkeypatch, valid_blacklist_words_response_for_blacklist_word_match, product_data, is_blacklisted_response, valid_list_of_reasons_for_release_validation, validation_error_constructor_data): """Test successful retrieval of blacklist words culprit.""" mocker.patch.object( features, "verify_feature_eligibility", return_value=False, ) mock_app.g.request_context.identity_id = "foo" monkeypatch.setattr( blacklist_reasons_model, 'get_blacklist_reasons_by_id', value=MagicMock( return_value=response.Response( message=valid_list_of_reasons_for_release_validation))) result = blacklist_words._is_blacklisted_text( product_data, valid_blacklist_words_response_for_blacklist_word_match, blacklist_words.STANDARD_REGEX, None) if is_blacklisted_response: assert result == validation_error_constructor_data else: assert result is is_blacklisted_response @pytest.mark.parametrize( ('product_data', 'product_dict', 'is_blacklisted_response'), [ ('my name is reina test', None, True), ('reniatest', None, False), ('reina', None, True), ('reina name', None, True), ('test reina m', None, True), ('123reina', None, False), ('Reinareina', None, False), ]) def test_is_blacklisted_text_product_regex( mock_app, mocker, monkeypatch, valid_blacklist_words_response_for_blacklist_word_match, product_data, product_dict, is_blacklisted_response, valid_list_of_reasons_for_release_validation, validation_error_constructor_data): """Test successful retrieval of blacklist words culprit.""" mocker.patch.object( features, "verify_feature_eligibility", return_value=False, ) mock_app.g.request_context.identity_id = "foo" monkeypatch.setattr( blacklist_reasons_model, 'get_blacklist_reasons_by_id', value=MagicMock( return_value=response.Response( message=valid_list_of_reasons_for_release_validation))) result = blacklist_words._is_blacklisted_text( '\n{}\n'.format(product_data), valid_blacklist_words_response_for_blacklist_word_match, blacklist_words.PRODUCT_REGEX, product_dict) if is_blacklisted_response: assert result == validation_error_constructor_data else: assert result is is_blacklisted_response def test_is_blacklisted_text_match_fields( mock_app, mocker, monkeypatch, valid_blacklist_words_response_for_blacklist_word_match, valid_list_of_reasons_for_release_validation): """Test successful retrieval of blacklist words culprit.""" mocker.patch.object( features, "verify_feature_eligibility", return_value=False, ) mock_app.g.request_context.identity_id = "foo" product_dict = { 'test reina test': ['a'], 'testreinatest': ['b'], 'reina': ['c'], 'test': ['d'], '123reina': ['e'], 'reina reina': ['f'], 'reina,test': ['g'] } product_data = '\n'.join(product_dict.keys()) monkeypatch.setattr( blacklist_reasons_model, 'get_blacklist_reasons_by_id', value=MagicMock( return_value=response.Response( message=valid_list_of_reasons_for_release_validation))) result = blacklist_words._is_blacklisted_text( product_data, valid_blacklist_words_response_for_blacklist_word_match, blacklist_words.PRODUCT_REGEX, None, product_dict) assert result == { 'matched_blacklist_words': ['reina'], 'items': [ { 'word': 'reina', 'reason': 'Valid Reason', 'alert': 'Alert of reason', 'contact': 'schauhan@theorchard.com', 'matched_field': 'a' }, { 'word': 'reina', 'reason': 'Valid Reason', 'alert': 'Alert of reason', 'contact': 'schauhan@theorchard.com', 'matched_field': 'c' }, { 'word': 'reina', 'reason': 'Valid Reason', 'alert': 'Alert of reason', 'contact': 'schauhan@theorchard.com', 'matched_field': 'f' }, { 'word': 'reina', 'reason': 'Valid Reason', 'alert': 'Alert of reason', 'contact': 'schauhan@theorchard.com', 'matched_field': 'g' } ] } def test_validate_product_for_blacklisting_error( monkeypatch, fixture_product_data): """Test failure in retrieval of blacklist words.""" monkeypatch.setattr( persister, 'fetch_product_details_by_id', value=MagicMock( return_value=response.Response( message=fixture_product_data))) mock_response = response.Response(status=500) monkeypatch.setattr( blacklist_words_model, 'get_blacklist_words', value=MagicMock(return_value=mock_response)) result = blacklist_words.validate_product_for_blacklisting(100) assert result.status == 500 @pytest.mark.parametrize( ('text_to_check', 'expected_status'), [ ({'text': 'Blacklist Word abc'}, 400), ({'text': 'Billie Eilish'}, 200) ]) def test_validate_text_for_blacklisting( mock_app, mocker, monkeypatch, text_to_check, expected_status, valid_response_for_release_validation, valid_list_of_reasons_for_release_validation): """Test validation of text against blacklist words.""" mocker.patch.object( features, "verify_feature_eligibility", return_value=False, ) mock_app.g.request_context.identity_id = "foo" monkeypatch.setattr( blacklist_words_model, 'get_blacklist_words_for_validation', value=MagicMock( return_value=response.Response( message=valid_response_for_release_validation))) monkeypatch.setattr( blacklist_reasons_model, 'get_blacklist_reasons_by_id', value=MagicMock( return_value=response.Response( message=valid_list_of_reasons_for_release_validation))) result = blacklist_words.validate_text_for_blacklisting( text_to_check) if expected_status == 200: expected_response = None elif expected_status == 400: expected_response = { 'validation_error': { 'matched_blacklist_words': ['Blacklist Word', 'abc'], 'items': [] } } assert result.message == expected_response assert result.status == expected_status def test_validate_text_for_blacklisting_error( monkeypatch, fixture_product_data): """Test failure in retrieval of blacklist words.""" monkeypatch.setattr( persister, 'fetch_product_details_by_id', value=MagicMock( return_value=response.Response( message=fixture_product_data))) mock_response = response.Response(status=500) monkeypatch.setattr( blacklist_words_model, 'get_blacklist_words', value=MagicMock(return_value=mock_response)) result = blacklist_words.validate_text_for_blacklisting({'text': 'test'}) assert result.status == 500 @pytest.mark.parametrize( "track_mock_return, product_mock_return, expected_status, expected_message", [ # Case 1: no matches → 200 ([], [], 200, None), # Case 2: both have matches → 400 ( [{"tuid": 1, "matches": [{"name": "A", "reason": "R"}]}], [{"name": "B", "reason": "R"}], 400, { "product_id": 2, "validation_errors": { "track_artists": [{"tuid": 1, "matches": [{"name": "A", "reason": "R"}]}], "product_artists": {'matches': [{"name": "B", "reason": "R"}]} } } ), ] ) def test_validate_artists_for_blacklisting( monkeypatch, track_mock_return, product_mock_return, expected_status, expected_message): """Test validate_artists_for_blacklisting dispatches correctly and returns the right status/message.""" input_data = { "track_artists": [{"artists": [{"name": "A"}], "tuid": 1}], "product_artists": [{"name": "B"}], "product_id": 2 } lookup_data = ({"SomeWord"}, {"SomeWord": 1}, {1: {"reason": "R"}}) monkeypatch.setattr( blacklist_words, "_get_blacklist_words_and_reasons", MagicMock(return_value=response.Response(message=lookup_data, status=200)) ) track_mock = MagicMock(return_value=track_mock_return) product_mock = MagicMock(return_value=product_mock_return) monkeypatch.setattr(blacklist_words, "_validate_track_artists", track_mock) monkeypatch.setattr(blacklist_words, "_validate_product_artists", product_mock) result = blacklist_words.validate_artists_for_blacklisting(input_data) assert result.status == expected_status assert result.message == expected_message track_mock.assert_called_once_with(input_data, lookup_data) product_mock.assert_called_once_with(input_data, lookup_data) def test_validate_artists_for_blacklisting_lookup_failure(monkeypatch): """Test validate_artists_for_blacklisting returns early when lookup fails.""" input_data = { "track_artists": [{"artists": [{"name": "A"}], "tuid": 1}], "product_artists": [{"name": "B"}], "product_id": 2 } error_response = response.Response(status=500) monkeypatch.setattr( blacklist_words, "_get_blacklist_words_and_reasons", MagicMock(return_value=error_response) ) track_mock = MagicMock() product_mock = MagicMock() monkeypatch.setattr(blacklist_words, "_validate_track_artists", track_mock) monkeypatch.setattr(blacklist_words, "_validate_product_artists", product_mock) result = blacklist_words.validate_artists_for_blacklisting(input_data) assert result.status == 500 track_mock.assert_not_called() product_mock.assert_not_called() @pytest.mark.parametrize( "input_data, expected_matches", [ # Case 1: artist is blacklisted — returns list with match ( { "product_artists": [ {"name": "BadArtist", "role": "main"}, {"name": "GoodArtist", "role": "feature"} ], "product_id": 123 }, [{"name": "BadArtist", "role": "main", "reason": "Reason1"}] ), # Case 2: no artist is blacklisted — returns empty list ( {"product_artists": [{"name": "GoodArtist", "role": "feature"}], "product_id": 456}, [] ), ] ) def test__validate_product_artists_param(input_data, expected_matches): """Test _validate_product_artists returns correct match list.""" mock_blacklist_lookup_data = ( {"badartist"}, {"badartist": 1}, {1: {"reason": "Reason1", "alert": "Alert1", "contact": "Contact1"}} ) result = blacklist_words._validate_product_artists(input_data, mock_blacklist_lookup_data) assert result == expected_matches @pytest.mark.parametrize( "input_data, expected_matches", [ # Case 1: artist is blacklisted — returns list with match ( { "track_artists": [ { "artists": [ {"name": "BadArtist", "role": "main"}, {"name": "GoodArtist0", "role": "feature"}, ], "tuid": 10}, {"artists": [{"name": "GoodArtist1", "role": "feature"}], "tuid": 11}, ] }, [{"tuid": 10, "matches": [{"name": "BadArtist", "role": "main", "reason": "Reason2"}]}] ), # Case 2: no artist is blacklisted — returns empty list ( { "track_artists": [{"artists": [{"name": "GoodArtist", "role": "feature"}], "tuid": 11}] }, [] ), ] ) def test__validate_track_artists_param(input_data, expected_matches): """Test _validate_track_artists returns correct match list.""" mock_blacklist_lookup_data = ( {"badartist"}, {"badartist": 2}, {2: {"reason": "Reason2", "alert": "Alert2", "contact": "Contact2"}} ) result = blacklist_words._validate_track_artists(input_data, mock_blacklist_lookup_data) assert result == expected_matches @pytest.mark.parametrize( "reason_ids", [ # Case 1: empty reason_ids — routes to get_blacklist_words_for_validation None, # Case 2: non-empty reason_ids — routes to get_blacklist_words_by_reason_ids (1,), ] ) def test__get_blacklist_words_and_reasons_success(monkeypatch, reason_ids): """Test _get_blacklist_words_and_reasons returns 200 for empty and non-empty reason_ids.""" blacklist_words._get_blacklist_words_and_reasons.cache_clear() mock_session = MagicMock() mock_cm = MagicMock() mock_cm.__enter__ = MagicMock(return_value=mock_session) mock_cm.__exit__ = MagicMock(return_value=False) monkeypatch.setattr(mysql, "ar_db_ro_session", MagicMock(return_value=mock_cm)) words = [{"word": "bad_word", "reason_id": 1}] reasons_data = {1: {"reason": "R1", "alert": "A1", "contact": "C1"}} words_response = MagicMock(status=200, message={'items': words}) reasons_response = response.Response(status=200, message=reasons_data) mock_for_validation = MagicMock(return_value=words_response) mock_by_reason_ids = MagicMock(return_value=words_response) monkeypatch.setattr(blacklist_words_model, "get_blacklist_words_for_validation", mock_for_validation) monkeypatch.setattr(blacklist_words_model, "get_blacklist_words_by_reason_ids", mock_by_reason_ids) monkeypatch.setattr( blacklist_reasons_model, "get_blacklist_reasons_by_id", MagicMock(return_value=reasons_response)) result = blacklist_words._get_blacklist_words_and_reasons(reason_ids) assert result.status == 200 word_set, word_to_reason_id, reasons = result.message assert word_set == {"bad_word"} assert word_to_reason_id == {"bad_word": 1} assert reasons == reasons_data if reason_ids: mock_by_reason_ids.assert_called_once() mock_for_validation.assert_not_called() else: mock_for_validation.assert_called_once() mock_by_reason_ids.assert_not_called() @pytest.mark.parametrize( "blacklist_words_status, reasons_status, expected_status", [ # Case 1: blacklist_words_result fails (500, 200, 500), # Case 2: reasons_result fails (200, 500, 500), ] ) def test__get_blacklist_words_and_reasons_failure( monkeypatch, blacklist_words_status, reasons_status, expected_status): """Test _get_blacklist_words_and_reasons returns an error when blacklist_words or reasons call fails.""" blacklist_words._get_blacklist_words_and_reasons.cache_clear() mock_session = MagicMock() mock_cm = MagicMock() mock_cm.__enter__ = MagicMock(return_value=mock_session) mock_cm.__exit__ = MagicMock(return_value=False) monkeypatch.setattr(mysql, "ar_db_ro_session", MagicMock(return_value=mock_cm)) if blacklist_words_status != 200: words_response = MagicMock(status=blacklist_words_status) else: words_response = MagicMock(status=200, message={ 'items': [{"word": "bad_word", "reason_id": 1}] }) monkeypatch.setattr( blacklist_words_model, "get_blacklist_words_for_validation", MagicMock(return_value=words_response)) monkeypatch.setattr( blacklist_words_model, "get_blacklist_words_by_reason_ids", MagicMock(return_value=words_response)) monkeypatch.setattr( blacklist_reasons_model, "get_blacklist_reasons_by_id", MagicMock(return_value=response.Response(status=reasons_status)) ) result = blacklist_words._get_blacklist_words_and_reasons(None) assert result.status == expected_status @pytest.mark.parametrize( "artist_group, blacklist_word_set, word_to_reason_id, reasons, expected", [ # Case 1: no matches - return empty list ( [ {"name": "GoodArtist", "role": "feature"}, ], {"bad artist"}, {"bad artist": 3}, {3: {"reason": "Blocked", "alert": "alert", "contact": "contact"}}, [], ), # Case 2: multiple artists, only the blacklisted one is returned ( [ {"name": "GoodArtist", "role": "feature"}, {"name": "BAD ARTIST", "role": "main"}, ], {"bad artist"}, {"bad artist": 3}, {3: {"reason": "Blocked", "alert": "alert", "contact": "contact"}}, [{"name": "BAD ARTIST", "role": "main", "reason": "Blocked"}], ), # Case 3: artist has extra fields - only name, role, and reason are returned (type absent) ( [ {"name": "BAD ARTIST", "role": "main", "id": 42, "extra_field": "should not appear"}, ], {"bad artist"}, {"bad artist": 3}, {3: {"reason": "Blocked", "alert": "alert", "contact": "contact"}}, [{"name": "BAD ARTIST", "role": "main", "reason": "Blocked"}], ), # Case 4: artist has type but no role - type is included, role is not ( [ {"name": "BAD ARTIST", "type": "primary"}, ], {"bad artist"}, {"bad artist": 3}, {3: {"reason": "Blocked", "alert": "alert", "contact": "contact"}}, [{"name": "BAD ARTIST", "type": "primary", "reason": "Blocked"}], ) ], ) def test__parse_artists_lowercase_conversion( artist_group, blacklist_word_set, word_to_reason_id, reasons, expected): """Test _parse_artists converts artist names to lowercase before lookup.""" result = blacklist_words._parse_artists( artist_group, blacklist_word_set, word_to_reason_id, reasons) assert result == expected