#!/usr/bin/env python3 """ Unit Tests for Social Media Audit Functionality ================================================ Tests for: - WebsiteAuditor (scripts/social_media_audit.py) - GooglePlacesSearcher (scripts/social_media_audit.py) - BraveSearcher (scripts/social_media_audit.py) - SocialMediaAuditor (scripts/social_media_audit.py) Run tests: cd tests python -m pytest test_social_media_audit.py -v Author: Maciej Pienczyn, InPi sp. z o.o. Date: 2026-01-08 """ import json import sys import unittest from datetime import datetime, date, timedelta from pathlib import Path from unittest.mock import Mock, MagicMock, patch, PropertyMock # Add scripts directory to path for imports sys.path.insert(0, str(Path(__file__).parent.parent / 'scripts')) # Import modules to test from social_media_audit import ( WebsiteAuditor, GooglePlacesSearcher, BraveSearcher, SocialMediaAuditor, HOSTING_PROVIDERS, SOCIAL_MEDIA_PATTERNS, SOCIAL_MEDIA_EXCLUDE, ) # ============================================================================ # WebsiteAuditor Tests # ============================================================================ class TestWebsiteAuditor(unittest.TestCase): """Tests for WebsiteAuditor class.""" def setUp(self): """Set up test with WebsiteAuditor instance.""" self.auditor = WebsiteAuditor() def test_auditor_initialization(self): """Test auditor initializes correctly.""" self.assertIsNotNone(self.auditor.session) self.assertIn('User-Agent', self.auditor.session.headers) def test_audit_website_empty_url(self): """Test audit with empty URL.""" result = self.auditor.audit_website('') self.assertEqual(result['url'], '') self.assertIn('No URL provided', result['errors']) def test_audit_website_none_url(self): """Test audit with None URL.""" result = self.auditor.audit_website(None) self.assertIn('No URL provided', result['errors']) @patch.object(WebsiteAuditor, '_check_ssl') @patch.object(WebsiteAuditor, '_detect_hosting') @patch('requests.Session.get') def test_audit_website_success(self, mock_get, mock_hosting, mock_ssl): """Test successful website audit.""" mock_ssl.return_value = { 'ssl_valid': True, 'ssl_expiry': date(2027, 1, 1), 'ssl_issuer': "Let's Encrypt", } mock_hosting.return_value = { 'hosting_provider': 'OVH', 'hosting_ip': '51.38.1.1', } mock_response = Mock() mock_response.status_code = 200 mock_response.url = 'https://example.com/' mock_response.headers = { 'Server': 'nginx/1.18.0', 'Last-Modified': 'Tue, 07 Jan 2026 10:00:00 GMT', } mock_response.text = '''
Facebook ''' mock_get.return_value = mock_response result = self.auditor.audit_website('https://example.com') self.assertEqual(result['http_status'], 200) self.assertTrue(result['has_ssl']) self.assertTrue(result['ssl_valid']) self.assertTrue(result['has_viewport_meta']) self.assertTrue(result['is_mobile_friendly']) self.assertEqual(result['site_author'], 'Test Author') self.assertEqual(result['site_generator'], 'WordPress 6.0') @patch('requests.Session.get') def test_audit_website_ssl_error_fallback(self, mock_get): """Test SSL error with HTTP fallback.""" # First call (HTTPS) raises SSL error # Second call (HTTP) succeeds mock_http_response = Mock() mock_http_response.status_code = 200 mock_http_response.url = 'http://example.com/' mock_http_response.text = '' mock_http_response.headers = {} import requests mock_get.side_effect = [ requests.exceptions.SSLError('Certificate verify failed'), mock_http_response, ] result = self.auditor.audit_website('https://example.com') self.assertEqual(result['http_status'], 200) self.assertFalse(result['has_ssl']) self.assertFalse(result['ssl_valid']) self.assertIn('SSL Error', result['errors'][0]) def test_audit_website_normalizes_url(self): """Test URL normalization adds https://.""" with patch.object(self.auditor, '_check_ssl') as mock_ssl, \ patch.object(self.auditor, '_detect_hosting') as mock_hosting, \ patch('requests.Session.get') as mock_get: mock_ssl.return_value = {} mock_hosting.return_value = {} mock_response = Mock() mock_response.status_code = 200 mock_response.url = 'https://example.com/' mock_response.text = '' mock_response.headers = {} mock_get.return_value = mock_response result = self.auditor.audit_website('example.com') # Verify the URL was normalized before making request mock_get.assert_called() class TestWebsiteAuditorSSL(unittest.TestCase): """Tests for SSL checking functionality.""" def setUp(self): """Set up test with WebsiteAuditor instance.""" self.auditor = WebsiteAuditor() @patch('socket.create_connection') @patch('ssl.create_default_context') def test_check_ssl_valid_certificate(self, mock_context, mock_connection): """Test SSL check with valid certificate.""" mock_sock = MagicMock() mock_connection.return_value.__enter__.return_value = mock_sock mock_ssock = MagicMock() mock_ssock.getpeercert.return_value = { 'notAfter': 'Jan 1 00:00:00 2027 GMT', 'issuer': ((('organizationName', "Let's Encrypt"),),), } mock_context.return_value.wrap_socket.return_value.__enter__.return_value = mock_ssock result = self.auditor._check_ssl('example.com') self.assertTrue(result['ssl_valid']) self.assertEqual(result['ssl_expiry'], date(2027, 1, 1)) self.assertEqual(result['ssl_issuer'], "Let's Encrypt") @patch('socket.create_connection') def test_check_ssl_connection_error(self, mock_connection): """Test SSL check with connection error.""" mock_connection.side_effect = ConnectionRefusedError('Connection refused') result = self.auditor._check_ssl('example.com') self.assertFalse(result['ssl_valid']) class TestWebsiteAuditorHosting(unittest.TestCase): """Tests for hosting detection functionality.""" def setUp(self): """Set up test with WebsiteAuditor instance.""" self.auditor = WebsiteAuditor() @patch('socket.gethostbyname') def test_detect_hosting_by_ip_prefix(self, mock_gethostbyname): """Test hosting detection by IP prefix.""" mock_gethostbyname.return_value = '51.38.123.45' result = self.auditor._detect_hosting('example.com') self.assertEqual(result['hosting_ip'], '51.38.123.45') self.assertEqual(result['hosting_provider'], 'OVH') @patch('socket.gethostbyname') def test_detect_hosting_cloudflare(self, mock_gethostbyname): """Test Cloudflare hosting detection.""" mock_gethostbyname.return_value = '104.16.123.45' result = self.auditor._detect_hosting('example.com') self.assertEqual(result['hosting_provider'], 'Cloudflare') @patch('socket.gethostbyname') @patch('socket.gethostbyaddr') def test_detect_hosting_by_reverse_dns(self, mock_reverse, mock_gethostbyname): """Test hosting detection by reverse DNS.""" mock_gethostbyname.return_value = '192.168.1.1' # Unknown IP mock_reverse.return_value = ('server.nazwa.pl', [], []) result = self.auditor._detect_hosting('example.com') self.assertEqual(result['hosting_provider'], 'nazwa.pl') class TestWebsiteAuditorHTMLParsing(unittest.TestCase): """Tests for HTML parsing functionality.""" def setUp(self): """Set up test with WebsiteAuditor instance.""" self.auditor = WebsiteAuditor() def test_parse_html_viewport_meta(self): """Test viewport meta detection.""" html = ''' ''' result = self.auditor._parse_html(html) self.assertTrue(result['has_viewport_meta']) self.assertTrue(result['is_mobile_friendly']) def test_parse_html_no_viewport(self): """Test detection when viewport is missing.""" html = '' result = self.auditor._parse_html(html) self.assertFalse(result['has_viewport_meta']) self.assertFalse(result['is_mobile_friendly']) def test_parse_html_author_meta(self): """Test author meta extraction.""" html = ''' ''' result = self.auditor._parse_html(html) self.assertEqual(result['site_author'], 'Test Company') def test_parse_html_generator_meta(self): """Test generator/CMS detection.""" html = ''' ''' result = self.auditor._parse_html(html) self.assertEqual(result['site_generator'], 'WordPress 6.4') def test_parse_html_footer_author(self): """Test author extraction from footer.""" html = ''' ''' result = self.auditor._parse_html(html) self.assertIsNotNone(result['site_author']) self.assertIn('Test Agency', result['site_author']) def test_parse_html_social_media_facebook(self): """Test Facebook link extraction.""" html = ''' Facebook ''' result = self.auditor._parse_html(html) self.assertIn('facebook', result['social_media_links']) self.assertEqual(result['social_media_links']['facebook'], 'https://facebook.com/testcompany') def test_parse_html_social_media_instagram(self): """Test Instagram link extraction.""" html = ''' Instagram ''' result = self.auditor._parse_html(html) self.assertIn('instagram', result['social_media_links']) def test_parse_html_social_media_excludes_share_links(self): """Test that share/sharer links are excluded.""" html = ''' Share Share ''' result = self.auditor._parse_html(html) # Should not extract share links self.assertEqual(result['social_media_links'], {}) def test_parse_html_all_social_platforms(self): """Test extraction of all social media platforms.""" html = ''' FB IG YT LI TT TW ''' result = self.auditor._parse_html(html) self.assertIn('facebook', result['social_media_links']) self.assertIn('instagram', result['social_media_links']) self.assertIn('youtube', result['social_media_links']) self.assertIn('linkedin', result['social_media_links']) self.assertIn('tiktok', result['social_media_links']) self.assertIn('twitter', result['social_media_links']) # ============================================================================ # GooglePlacesSearcher Tests # ============================================================================ class TestGooglePlacesSearcher(unittest.TestCase): """Tests for GooglePlacesSearcher class.""" def setUp(self): """Set up test with GooglePlacesSearcher instance.""" self.searcher = GooglePlacesSearcher(api_key='test_api_key') def test_searcher_initialization_with_api_key(self): """Test searcher initializes with provided API key.""" searcher = GooglePlacesSearcher(api_key='my_api_key') self.assertEqual(searcher.api_key, 'my_api_key') @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'env_api_key'}) def test_searcher_initialization_from_env(self): """Test searcher initializes from environment variable.""" searcher = GooglePlacesSearcher() self.assertEqual(searcher.api_key, 'env_api_key') def test_searcher_initialization_no_api_key(self): """Test searcher handles missing API key.""" with patch.dict('os.environ', {}, clear=True): searcher = GooglePlacesSearcher(api_key=None) self.assertIsNone(searcher.api_key) class TestGooglePlacesSearcherFindPlace(unittest.TestCase): """Tests for GooglePlacesSearcher.find_place method.""" def setUp(self): """Set up test with GooglePlacesSearcher instance.""" self.searcher = GooglePlacesSearcher(api_key='test_api_key') def test_find_place_no_api_key(self): """Test find_place returns None without API key.""" searcher = GooglePlacesSearcher(api_key=None) result = searcher.find_place('Test Company') self.assertIsNone(result) @patch('requests.Session.get') def test_find_place_success(self, mock_get): """Test successful place search.""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { 'status': 'OK', 'candidates': [ { 'place_id': 'ChIJN1t_tDeuEmsRUsoyG83frY4', 'name': 'Test Company', 'formatted_address': 'Wejherowo, Poland', } ] } mock_response.raise_for_status = Mock() mock_get.return_value = mock_response result = self.searcher.find_place('Test Company', 'Wejherowo') self.assertEqual(result, 'ChIJN1t_tDeuEmsRUsoyG83frY4') mock_get.assert_called_once() # Verify API call parameters call_args = mock_get.call_args params = call_args.kwargs.get('params', call_args[1].get('params', {})) self.assertEqual(params['inputtype'], 'textquery') self.assertIn('Test Company', params['input']) self.assertEqual(params['language'], 'pl') self.assertEqual(params['key'], 'test_api_key') @patch('requests.Session.get') def test_find_place_zero_results(self, mock_get): """Test find_place with no results.""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { 'status': 'ZERO_RESULTS', 'candidates': [] } mock_response.raise_for_status = Mock() mock_get.return_value = mock_response result = self.searcher.find_place('Nonexistent Company') self.assertIsNone(result) @patch('requests.Session.get') def test_find_place_api_error(self, mock_get): """Test find_place handles API errors.""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { 'status': 'REQUEST_DENIED', 'error_message': 'Invalid API key', } mock_response.raise_for_status = Mock() mock_get.return_value = mock_response result = self.searcher.find_place('Test Company') self.assertIsNone(result) @patch('requests.Session.get') def test_find_place_timeout(self, mock_get): """Test find_place handles timeout.""" import requests mock_get.side_effect = requests.exceptions.Timeout('Connection timed out') result = self.searcher.find_place('Test Company') self.assertIsNone(result) @patch('requests.Session.get') def test_find_place_request_exception(self, mock_get): """Test find_place handles request exceptions.""" import requests mock_get.side_effect = requests.exceptions.RequestException('Network error') result = self.searcher.find_place('Test Company') self.assertIsNone(result) class TestGooglePlacesSearcherGetDetails(unittest.TestCase): """Tests for GooglePlacesSearcher.get_place_details method.""" def setUp(self): """Set up test with GooglePlacesSearcher instance.""" self.searcher = GooglePlacesSearcher(api_key='test_api_key') def test_get_place_details_no_api_key(self): """Test get_place_details returns empty dict without API key.""" searcher = GooglePlacesSearcher(api_key=None) result = searcher.get_place_details('some_place_id') self.assertIsNone(result['google_rating']) self.assertIsNone(result['google_reviews_count']) def test_get_place_details_no_place_id(self): """Test get_place_details returns empty dict without place_id.""" result = self.searcher.get_place_details(None) self.assertIsNone(result['google_rating']) self.assertIsNone(result['google_reviews_count']) def test_get_place_details_empty_place_id(self): """Test get_place_details returns empty dict with empty place_id.""" result = self.searcher.get_place_details('') self.assertIsNone(result['google_rating']) self.assertIsNone(result['google_reviews_count']) @patch('requests.Session.get') def test_get_place_details_success(self, mock_get): """Test successful place details retrieval.""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { 'status': 'OK', 'result': { 'name': 'Test Company', 'rating': 4.5, 'user_ratings_total': 123, 'opening_hours': { 'weekday_text': [ 'Monday: 8:00 AM – 6:00 PM', 'Tuesday: 8:00 AM – 6:00 PM', ], 'open_now': True, 'periods': [], }, 'business_status': 'OPERATIONAL', 'formatted_phone_number': '+48 58 123 4567', 'website': 'https://example.com', } } mock_response.raise_for_status = Mock() mock_get.return_value = mock_response result = self.searcher.get_place_details('test_place_id') self.assertEqual(result['google_rating'], 4.5) self.assertEqual(result['google_reviews_count'], 123) self.assertEqual(result['business_status'], 'OPERATIONAL') self.assertEqual(result['formatted_phone'], '+48 58 123 4567') self.assertEqual(result['website'], 'https://example.com') self.assertIsNotNone(result['opening_hours']) self.assertTrue(result['opening_hours']['open_now']) @patch('requests.Session.get') def test_get_place_details_partial_data(self, mock_get): """Test place details with partial data.""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { 'status': 'OK', 'result': { 'name': 'Test Company', 'rating': 3.8, # No review count, opening hours, etc. } } mock_response.raise_for_status = Mock() mock_get.return_value = mock_response result = self.searcher.get_place_details('test_place_id') self.assertEqual(result['google_rating'], 3.8) self.assertIsNone(result['google_reviews_count']) self.assertIsNone(result['opening_hours']) self.assertIsNone(result['business_status']) @patch('requests.Session.get') def test_get_place_details_api_error(self, mock_get): """Test get_place_details handles API errors.""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { 'status': 'NOT_FOUND', } mock_response.raise_for_status = Mock() mock_get.return_value = mock_response result = self.searcher.get_place_details('invalid_place_id') self.assertIsNone(result['google_rating']) self.assertIsNone(result['google_reviews_count']) @patch('requests.Session.get') def test_get_place_details_timeout(self, mock_get): """Test get_place_details handles timeout.""" import requests mock_get.side_effect = requests.exceptions.Timeout('Connection timed out') result = self.searcher.get_place_details('test_place_id') self.assertIsNone(result['google_rating']) self.assertIsNone(result['google_reviews_count']) @patch('requests.Session.get') def test_get_place_details_correct_fields_requested(self, mock_get): """Test that correct fields are requested from API.""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = {'status': 'OK', 'result': {}} mock_response.raise_for_status = Mock() mock_get.return_value = mock_response self.searcher.get_place_details('test_place_id') # Verify API call includes required fields call_args = mock_get.call_args params = call_args.kwargs.get('params', call_args[1].get('params', {})) fields = params['fields'].split(',') self.assertIn('rating', fields) self.assertIn('user_ratings_total', fields) self.assertIn('opening_hours', fields) self.assertIn('business_status', fields) self.assertIn('formatted_phone_number', fields) self.assertIn('website', fields) class TestGooglePlacesSearcherIntegration(unittest.TestCase): """Integration tests for GooglePlacesSearcher.""" def setUp(self): """Set up test with GooglePlacesSearcher instance.""" self.searcher = GooglePlacesSearcher(api_key='test_api_key') @patch('requests.Session.get') def test_find_and_get_details_workflow(self, mock_get): """Test complete workflow: find place, then get details.""" # Mock find_place response find_response = Mock() find_response.status_code = 200 find_response.json.return_value = { 'status': 'OK', 'candidates': [ { 'place_id': 'test_place_id_123', 'name': 'PIXLAB Sp. z o.o.', 'formatted_address': 'Wejherowo, Poland', } ] } find_response.raise_for_status = Mock() # Mock get_place_details response details_response = Mock() details_response.status_code = 200 details_response.json.return_value = { 'status': 'OK', 'result': { 'name': 'PIXLAB Sp. z o.o.', 'rating': 4.9, 'user_ratings_total': 45, 'business_status': 'OPERATIONAL', 'opening_hours': { 'weekday_text': ['Monday: 9:00 AM – 5:00 PM'], 'open_now': False, }, } } details_response.raise_for_status = Mock() mock_get.side_effect = [find_response, details_response] # Execute workflow place_id = self.searcher.find_place('PIXLAB', 'Wejherowo') self.assertEqual(place_id, 'test_place_id_123') details = self.searcher.get_place_details(place_id) self.assertEqual(details['google_rating'], 4.9) self.assertEqual(details['google_reviews_count'], 45) self.assertEqual(details['business_status'], 'OPERATIONAL') # ============================================================================ # BraveSearcher Tests # ============================================================================ class TestBraveSearcher(unittest.TestCase): """Tests for BraveSearcher class.""" def setUp(self): """Set up test with BraveSearcher instance.""" self.searcher = BraveSearcher(api_key='test_brave_api_key') def test_searcher_initialization_with_api_key(self): """Test searcher initializes with provided API key.""" searcher = BraveSearcher(api_key='my_brave_key') self.assertEqual(searcher.api_key, 'my_brave_key') @patch.dict('os.environ', {'BRAVE_API_KEY': 'env_brave_key'}) def test_searcher_initialization_from_env(self): """Test searcher initializes from environment variable.""" searcher = BraveSearcher() self.assertEqual(searcher.api_key, 'env_brave_key') class TestBraveSearcherGoogleReviews(unittest.TestCase): """Tests for BraveSearcher Google reviews functionality.""" def setUp(self): """Set up test with BraveSearcher instance.""" self.searcher = BraveSearcher(api_key='test_brave_api_key') @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_places_key'}) @patch.object(GooglePlacesSearcher, 'find_place') @patch.object(GooglePlacesSearcher, 'get_place_details') def test_search_google_reviews(self, mock_details, mock_find): """Test successful Google reviews retrieval via Places API.""" # Mock successful place lookup mock_find.return_value = 'ChIJN1t_tDeuEmsRUsoyG83frY4' # Mock complete place details response mock_details.return_value = { 'google_rating': 4.7, 'google_reviews_count': 156, 'opening_hours': { 'weekday_text': [ 'Monday: 8:00 AM – 5:00 PM', 'Tuesday: 8:00 AM – 5:00 PM', 'Wednesday: 8:00 AM – 5:00 PM', 'Thursday: 8:00 AM – 5:00 PM', 'Friday: 8:00 AM – 4:00 PM', 'Saturday: Closed', 'Sunday: Closed', ], 'open_now': True, 'periods': [], }, 'business_status': 'OPERATIONAL', 'formatted_phone': '+48 58 123 4567', 'website': 'https://example.com', } result = self.searcher.search_google_reviews('Test Company Sp. z o.o.', 'Wejherowo') # Verify all fields are correctly returned self.assertEqual(result['google_rating'], 4.7) self.assertEqual(result['google_reviews_count'], 156) self.assertIsNotNone(result['opening_hours']) self.assertTrue(result['opening_hours']['open_now']) self.assertEqual(len(result['opening_hours']['weekday_text']), 7) self.assertEqual(result['business_status'], 'OPERATIONAL') # Verify correct API calls were made mock_find.assert_called_once_with('Test Company Sp. z o.o.', 'Wejherowo') mock_details.assert_called_once_with('ChIJN1t_tDeuEmsRUsoyG83frY4') @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_places_key'}) @patch.object(GooglePlacesSearcher, 'find_place') @patch.object(GooglePlacesSearcher, 'get_place_details') def test_search_google_reviews_uses_places_api(self, mock_details, mock_find): """Test that search_google_reviews uses Google Places API when available.""" mock_find.return_value = 'test_place_id' mock_details.return_value = { 'google_rating': 4.5, 'google_reviews_count': 100, 'opening_hours': {'open_now': True}, 'business_status': 'OPERATIONAL', } result = self.searcher.search_google_reviews('Test Company', 'Wejherowo') self.assertEqual(result['google_rating'], 4.5) self.assertEqual(result['google_reviews_count'], 100) mock_find.assert_called_once_with('Test Company', 'Wejherowo') mock_details.assert_called_once_with('test_place_id') @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_places_key'}) @patch.object(GooglePlacesSearcher, 'find_place') def test_search_google_reviews_no_place_found(self, mock_find): """Test search_google_reviews when no place is found.""" mock_find.return_value = None result = self.searcher.search_google_reviews('Unknown Company', 'Wejherowo') self.assertIsNone(result['google_rating']) self.assertIsNone(result['google_reviews_count']) @patch.dict('os.environ', {}, clear=True) def test_search_google_reviews_no_api_keys(self): """Test search_google_reviews without any API keys.""" searcher = BraveSearcher(api_key=None) result = searcher.search_google_reviews('Test Company') self.assertIsNone(result['google_rating']) self.assertIsNone(result['google_reviews_count']) @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_places_key'}) @patch.object(GooglePlacesSearcher, 'find_place') @patch.object(GooglePlacesSearcher, 'get_place_details') def test_search_google_reviews_error(self, mock_details, mock_find): """Test search_google_reviews handles API errors gracefully.""" import requests # Mock find_place to succeed but get_place_details to raise an error mock_find.return_value = 'test_place_id' mock_details.side_effect = requests.exceptions.RequestException('API connection failed') result = self.searcher.search_google_reviews('Test Company', 'Wejherowo') # Should handle error gracefully and return None values self.assertIsNone(result['google_rating']) self.assertIsNone(result['google_reviews_count']) # Verify the API calls were made mock_find.assert_called_once_with('Test Company', 'Wejherowo') mock_details.assert_called_once_with('test_place_id') class TestBraveSearcherBraveReviews(unittest.TestCase): """Tests for Brave Search fallback for reviews.""" def setUp(self): """Set up test with BraveSearcher instance.""" self.searcher = BraveSearcher(api_key='test_brave_api_key') @patch('requests.Session.get') def test_search_brave_for_reviews_success(self, mock_get): """Test successful review extraction from Brave search.""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { 'web': { 'results': [ { 'title': 'Test Company - Google Maps', 'description': 'Ocena: 4,5 (123 opinii) - Test Company', } ] } } mock_response.raise_for_status = Mock() mock_get.return_value = mock_response result = self.searcher._search_brave_for_reviews('Test Company', 'Wejherowo') self.assertIsNotNone(result) self.assertEqual(result['google_rating'], 4.5) self.assertEqual(result['google_reviews_count'], 123) @patch('requests.Session.get') def test_search_brave_for_reviews_no_rating_found(self, mock_get): """Test Brave search when no rating is found.""" mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { 'web': { 'results': [ { 'title': 'Test Company Website', 'description': 'Some description without rating', } ] } } mock_response.raise_for_status = Mock() mock_get.return_value = mock_response result = self.searcher._search_brave_for_reviews('Test Company', 'Wejherowo') self.assertIsNone(result) def test_search_brave_for_reviews_no_api_key(self): """Test Brave search returns None without API key.""" searcher = BraveSearcher(api_key=None) result = searcher._search_brave_for_reviews('Test Company', 'Wejherowo') self.assertIsNone(result) @patch('requests.Session.get') def test_search_brave_for_reviews_timeout(self, mock_get): """Test Brave search handles timeout.""" import requests mock_get.side_effect = requests.exceptions.Timeout('Connection timed out') result = self.searcher._search_brave_for_reviews('Test Company', 'Wejherowo') self.assertIsNone(result) # ============================================================================ # SocialMediaAuditor Tests # ============================================================================ class TestSocialMediaAuditor(unittest.TestCase): """Tests for SocialMediaAuditor class.""" @patch('social_media_audit.create_engine') @patch('social_media_audit.sessionmaker') def setUp(self, mock_sessionmaker, mock_engine): """Set up test with mocked database.""" self.mock_session = MagicMock() mock_sessionmaker.return_value = Mock(return_value=self.mock_session) self.auditor = SocialMediaAuditor(database_url='postgresql://test:test@localhost/test') @patch('social_media_audit.create_engine') @patch('social_media_audit.sessionmaker') def test_auditor_initialization(self, mock_sessionmaker, mock_engine): """Test auditor initializes correctly.""" auditor = SocialMediaAuditor() self.assertIsNotNone(auditor.website_auditor) self.assertIsNotNone(auditor.brave_searcher) @patch('social_media_audit.create_engine') @patch('social_media_audit.sessionmaker') @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_key'}) def test_auditor_initializes_google_places(self, mock_sessionmaker, mock_engine): """Test auditor initializes Google Places searcher when API key available.""" auditor = SocialMediaAuditor() self.assertIsNotNone(auditor.google_places_searcher) class TestSocialMediaAuditorGetCompanies(unittest.TestCase): """Tests for SocialMediaAuditor.get_companies method.""" @patch('social_media_audit.create_engine') @patch('social_media_audit.sessionmaker') def setUp(self, mock_sessionmaker, mock_engine): """Set up test with mocked database.""" self.mock_session = MagicMock() mock_sessionmaker.return_value = Mock(return_value=MagicMock( __enter__=Mock(return_value=self.mock_session), __exit__=Mock(return_value=False) )) self.auditor = SocialMediaAuditor() def test_get_companies_by_ids(self): """Test fetching companies by specific IDs.""" mock_result = [ Mock(_mapping={'id': 1, 'name': 'Company A', 'slug': 'company-a', 'website': 'https://a.com', 'address_city': 'Wejherowo'}), Mock(_mapping={'id': 2, 'name': 'Company B', 'slug': 'company-b', 'website': 'https://b.com', 'address_city': 'Wejherowo'}), ] self.mock_session.execute.return_value = mock_result companies = self.auditor.get_companies(company_ids=[1, 2]) # Verify query was called self.mock_session.execute.assert_called_once() class TestSocialMediaAuditorAuditCompany(unittest.TestCase): """Tests for SocialMediaAuditor.audit_company method.""" @patch('social_media_audit.create_engine') @patch('social_media_audit.sessionmaker') def setUp(self, mock_sessionmaker, mock_engine): """Set up test with mocked database.""" mock_session = MagicMock() mock_sessionmaker.return_value = Mock(return_value=mock_session) self.auditor = SocialMediaAuditor() self.auditor.website_auditor = Mock() self.auditor.brave_searcher = Mock() self.auditor.google_places_searcher = None def test_audit_company_with_website(self): """Test auditing company with website.""" company = { 'id': 1, 'name': 'Test Company', 'slug': 'test-company', 'website': 'https://example.com', 'address_city': 'Wejherowo', } self.auditor.website_auditor.audit_website.return_value = { 'http_status': 200, 'has_ssl': True, 'social_media_links': {'facebook': 'https://facebook.com/test'}, } self.auditor.brave_searcher.search_social_media.return_value = {} self.auditor.brave_searcher.search_google_reviews.return_value = { 'google_rating': 4.5, 'google_reviews_count': 50, } result = self.auditor.audit_company(company) self.assertEqual(result['company_id'], 1) self.assertEqual(result['company_name'], 'Test Company') self.assertIn('facebook', result['social_media']) self.auditor.website_auditor.audit_website.assert_called_once_with('https://example.com') def test_audit_company_without_website(self): """Test auditing company without website.""" company = { 'id': 2, 'name': 'No Website Company', 'slug': 'no-website', 'website': None, 'address_city': 'Wejherowo', } self.auditor.brave_searcher.search_social_media.return_value = { 'facebook': 'https://facebook.com/found', } self.auditor.brave_searcher.search_google_reviews.return_value = {} result = self.auditor.audit_company(company) self.assertEqual(result['company_id'], 2) self.assertIn('No website URL', result['website'].get('errors', [])) self.assertIn('facebook', result['social_media']) @patch.dict('os.environ', {'GOOGLE_PLACES_API_KEY': 'test_key'}) def test_audit_company_uses_google_places(self): """Test auditing uses Google Places API when available.""" company = { 'id': 3, 'name': 'Google Test', 'slug': 'google-test', 'website': 'https://example.com', 'address_city': 'Wejherowo', } mock_places_searcher = Mock() mock_places_searcher.find_place.return_value = 'test_place_id' mock_places_searcher.get_place_details.return_value = { 'google_rating': 4.8, 'google_reviews_count': 200, 'business_status': 'OPERATIONAL', 'opening_hours': {'open_now': True}, } self.auditor.google_places_searcher = mock_places_searcher self.auditor.website_auditor.audit_website.return_value = {'social_media_links': {}} self.auditor.brave_searcher.search_social_media.return_value = {} result = self.auditor.audit_company(company) self.assertEqual(result['google_reviews']['google_rating'], 4.8) self.assertEqual(result['google_reviews']['google_reviews_count'], 200) mock_places_searcher.find_place.assert_called_once_with('Google Test', 'Wejherowo') class TestSocialMediaAuditorSaveResult(unittest.TestCase): """Tests for SocialMediaAuditor.save_audit_result method.""" @patch('social_media_audit.create_engine') @patch('social_media_audit.sessionmaker') def setUp(self, mock_sessionmaker, mock_engine): """Set up test with mocked database.""" self.mock_session = MagicMock() mock_sessionmaker.return_value = Mock(return_value=MagicMock( __enter__=Mock(return_value=self.mock_session), __exit__=Mock(return_value=False) )) self.auditor = SocialMediaAuditor() def test_save_audit_result_success(self): """Test saving audit result successfully.""" result = { 'company_id': 1, 'company_name': 'Test Company', 'audit_date': datetime.now(), 'website': { 'url': 'https://example.com', 'http_status': 200, 'has_ssl': True, }, 'social_media': { 'facebook': 'https://facebook.com/test', }, 'google_reviews': { 'google_rating': 4.5, 'google_reviews_count': 50, }, } success = self.auditor.save_audit_result(result) self.assertTrue(success) # Verify execute was called for website and social media self.assertTrue(self.mock_session.execute.called) self.mock_session.commit.assert_called_once() # ============================================================================ # Constants and Patterns Tests # ============================================================================ class TestHostingProviders(unittest.TestCase): """Tests for hosting provider patterns.""" def test_hosting_providers_structure(self): """Test that HOSTING_PROVIDERS has correct structure.""" self.assertIsInstance(HOSTING_PROVIDERS, dict) for provider, patterns in HOSTING_PROVIDERS.items(): self.assertIsInstance(provider, str) self.assertIsInstance(patterns, list) for pattern in patterns: self.assertIsInstance(pattern, str) def test_major_providers_included(self): """Test that major hosting providers are included.""" expected_providers = ['OVH', 'Cloudflare', 'Google Cloud', 'AWS', 'nazwa.pl', 'home.pl'] for provider in expected_providers: self.assertIn(provider, HOSTING_PROVIDERS) class TestSocialMediaPatterns(unittest.TestCase): """Tests for social media URL patterns.""" def test_social_media_patterns_structure(self): """Test that SOCIAL_MEDIA_PATTERNS has correct structure.""" self.assertIsInstance(SOCIAL_MEDIA_PATTERNS, dict) expected_platforms = ['facebook', 'instagram', 'youtube', 'linkedin', 'tiktok', 'twitter'] for platform in expected_platforms: self.assertIn(platform, SOCIAL_MEDIA_PATTERNS) def test_facebook_pattern_matches(self): """Test Facebook URL pattern matching.""" import re patterns = SOCIAL_MEDIA_PATTERNS['facebook'] test_urls = [ ('https://facebook.com/testpage', 'testpage'), ('https://www.facebook.com/testpage', 'testpage'), ('https://fb.com/testpage', 'testpage'), ] for url, expected_match in test_urls: matched = False for pattern in patterns: match = re.search(pattern, url, re.IGNORECASE) if match: self.assertEqual(match.group(1), expected_match) matched = True break self.assertTrue(matched, f"Pattern should match {url}") def test_instagram_pattern_matches(self): """Test Instagram URL pattern matching.""" import re patterns = SOCIAL_MEDIA_PATTERNS['instagram'] test_urls = [ ('https://instagram.com/testaccount', 'testaccount'), ('https://www.instagram.com/testaccount', 'testaccount'), ] for url, expected_match in test_urls: matched = False for pattern in patterns: match = re.search(pattern, url, re.IGNORECASE) if match: self.assertEqual(match.group(1), expected_match) matched = True break self.assertTrue(matched, f"Pattern should match {url}") class TestSocialMediaExcludes(unittest.TestCase): """Tests for social media exclusion patterns.""" def test_excludes_structure(self): """Test that SOCIAL_MEDIA_EXCLUDE has correct structure.""" self.assertIsInstance(SOCIAL_MEDIA_EXCLUDE, dict) def test_facebook_excludes_share_links(self): """Test that Facebook share links are excluded.""" excludes = SOCIAL_MEDIA_EXCLUDE['facebook'] self.assertIn('sharer', excludes) self.assertIn('share', excludes) def test_twitter_excludes_intent(self): """Test that Twitter intent links are excluded.""" excludes = SOCIAL_MEDIA_EXCLUDE['twitter'] self.assertIn('intent', excludes) self.assertIn('share', excludes) # ============================================================================ # Run Tests # ============================================================================ if __name__ == '__main__': # Run with verbose output unittest.main(verbosity=2)