Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1207 lines
45 KiB
Python
1207 lines
45 KiB
Python
#!/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 = '''
|
||
<html>
|
||
<head>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="author" content="Test Author">
|
||
<meta name="generator" content="WordPress 6.0">
|
||
</head>
|
||
<body>
|
||
<a href="https://facebook.com/testpage">Facebook</a>
|
||
</body>
|
||
</html>
|
||
'''
|
||
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 = '<html><head></head><body></body></html>'
|
||
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 = '<html></html>'
|
||
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 = '''
|
||
<html>
|
||
<head>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
</head>
|
||
<body></body>
|
||
</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 = '<html><head></head><body></body></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 = '''
|
||
<html>
|
||
<head>
|
||
<meta name="author" content="Test Company">
|
||
</head>
|
||
<body></body>
|
||
</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 = '''
|
||
<html>
|
||
<head>
|
||
<meta name="generator" content="WordPress 6.4">
|
||
</head>
|
||
<body></body>
|
||
</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 = '''
|
||
<html>
|
||
<head></head>
|
||
<body>
|
||
<footer>
|
||
Wykonanie: Test Agency
|
||
</footer>
|
||
</body>
|
||
</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 = '''
|
||
<html>
|
||
<head></head>
|
||
<body>
|
||
<a href="https://facebook.com/testcompany">Facebook</a>
|
||
</body>
|
||
</html>
|
||
'''
|
||
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 = '''
|
||
<html>
|
||
<head></head>
|
||
<body>
|
||
<a href="https://instagram.com/testcompany">Instagram</a>
|
||
</body>
|
||
</html>
|
||
'''
|
||
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 = '''
|
||
<html>
|
||
<head></head>
|
||
<body>
|
||
<a href="https://facebook.com/sharer">Share</a>
|
||
<a href="https://facebook.com/share">Share</a>
|
||
</body>
|
||
</html>
|
||
'''
|
||
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 = '''
|
||
<html>
|
||
<head></head>
|
||
<body>
|
||
<a href="https://facebook.com/testfb">FB</a>
|
||
<a href="https://instagram.com/testig">IG</a>
|
||
<a href="https://youtube.com/@testyt">YT</a>
|
||
<a href="https://linkedin.com/company/testli">LI</a>
|
||
<a href="https://tiktok.com/@testtk">TT</a>
|
||
<a href="https://twitter.com/testtw">TW</a>
|
||
</body>
|
||
</html>
|
||
'''
|
||
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)
|