import json from collections.abc import Iterator from typing import Any from unittest import mock import faker import pytest from ows_text_campaigns.adapters.scanner import ContentRecommendations, ContentScanner from ows_text_campaigns.adapters.scanner.exceptions import ( ContentScannerCompleteError, ContentScannerParsingError, ) class TestContentScanner: @pytest.fixture(autouse=True) def mock_cortex_complete(self) -> Iterator[mock.MagicMock]: target_path = "ows_text_campaigns.adapters.scanner.base.complete" forbidden_words = ["vape", "cannabis", "free loan"] def dynamic_logic(**kwargs: Any) -> str: prompt_list = kwargs.get("prompt", []) content = "" if isinstance(prompt_list, list): for item in prompt_list: if item.get("role") == "user": content = item.get("content", "") break found_violations = [] for word in forbidden_words: if word in content.lower(): found_violations.append({"phrase": word}) response_data = {"phrases": found_violations} return json.dumps(response_data) with mock.patch(target_path) as m: m.side_effect = dynamic_logic yield m def test_scan_content_content_all_ok( self, scanner: ContentScanner, faker: faker.Faker ) -> None: artist_name = faker.name() sms = "I have released new single, come and listen. [PLACEHOLDER]." result = scanner.scan( artist_name=artist_name, content=sms.replace("[PLACEHOLDER]", artist_name), ) assert result == ContentRecommendations( sensitiveWords=[], includedArtistName=True ) def test_scan_content_content_ok_name_nok( self, scanner: ContentScanner, faker: faker.Faker ) -> None: artist_name = faker.name() sms = "I have released new single, come and listen." result = scanner.scan( artist_name=artist_name, content=sms.replace("[PLACEHOLDER]", artist_name), ) assert result == ContentRecommendations( sensitiveWords=[], includedArtistName=False ) def test_scan_content_content_multi_nok_name_nok( self, scanner: ContentScanner, faker: faker.Faker ) -> None: artist_name = faker.name() word1 = "cannabis" word2 = "free loan" sms = "We are offering [WORD1] and [WORD2] for everyone." result = scanner.scan( artist_name=artist_name, content=sms.replace("[WORD1]", word1).replace("[WORD2]", word2), ) assert result.sensitive_words is not None assert set(result.sensitive_words) == {word1, word2} assert not result.included_artist_name def test_scan_content_content_nok_name_nok( self, scanner: ContentScanner, faker: faker.Faker ) -> None: artist_name = faker.name() word1 = "cannabis" sms = "We are offering [WORD1] and music videos for everyone." result = scanner.scan( artist_name=artist_name, content=sms.replace("[WORD1]", word1), ) assert result.sensitive_words is not None assert result.sensitive_words[0] == word1 assert not result.included_artist_name def test_scan_content_nok_name_ok( self, scanner: ContentScanner, faker: faker.Faker ) -> None: artist_name = faker.name() word1 = "cannabis" word2 = "free loan" sms = "We are offering [WORD1] and [WORD2] for everyone. [PLACEHOLDER]." result = scanner.scan( artist_name=artist_name, content=sms.replace("[WORD1]", word1) .replace("[WORD2]", word2) .replace("[PLACEHOLDER]", artist_name), ) assert result.sensitive_words is not None assert set(result.sensitive_words) == {word1, word2} assert result.included_artist_name def test_scan_content_cortex_failure( self, scanner: ContentScanner, faker: faker.Faker, mock_cortex_complete: mock.MagicMock, ) -> None: artist_name = faker.name() sms = "I have released new single, come and listen. [PLACEHOLDER]." mock_cortex_complete.side_effect = Exception("Snowflake Connection Timeout") with pytest.raises(ContentScannerCompleteError) as exc_info: scanner.scan( artist_name=artist_name, content=sms, ) assert exc_info.value.code == "content_scanner_complete_error" def test_scan_content_llm_response_parsing_failure( self, scanner: ContentScanner, mock_cortex_complete: mock.MagicMock, faker: faker.Faker, ) -> None: artist_name = faker.name() content = "I have released new single, come and listen. [PLACEHOLDER]." mock_cortex_complete.side_effect = None mock_cortex_complete.expected_artist_name = artist_name mock_cortex_complete.return_value = "This is not JSON, this is plain text." with pytest.raises(ContentScannerParsingError) as exc_info: scanner.scan( artist_name=artist_name, content=content, ) assert exc_info.value.code == "content_scanner_parsing_error"