from services import trip_finder_service def test_scores_destination_and_duration_match_highest(): urls = [ 'https://intrepidtravel.com/peru-classic-15-days', 'https://intrepidtravel.com/blog/about-us', 'https://intrepidtravel.com/vietnam-explorer-10-days', ] result = trip_finder_service.score_and_rank_urls(urls, 'Peru', 15) assert result['url'] == 'https://intrepidtravel.com/peru-classic-15-days' assert result['found'] is True assert result['confidence'] > 0 def test_no_keyword_match_returns_not_found(): urls = ['https://intrepidtravel.com/about-us', 'https://intrepidtravel.com/contact'] result = trip_finder_service.score_and_rank_urls(urls, 'Peru', 15) assert result == {'url': None, 'confidence': 0, 'found': False} def test_empty_url_list_returns_not_found(): result = trip_finder_service.score_and_rank_urls([], 'Peru', 15) assert result == {'url': None, 'confidence': 0, 'found': False} def test_low_confidence_match_is_not_found(): # Only a weak category-keyword match, no destination/duration signal — # should fall below CONFIDENCE_FOUND_THRESHOLD (0.3). urls = ['https://intrepidtravel.com/some-other-trek'] result = trip_finder_service.score_and_rank_urls(urls, 'Peru', 15) assert result['found'] is False def test_wrong_destination_with_duration_and_category_match_is_not_found(): # Regression test — a Turkey URL previously scored 3/4 = 0.75 confidence # against a Peru search purely from duration ('9') + category ('classic') # matches, despite zero actual destination relevance. This is the exact # bug that served a Turkey trip for a Peru search via the fast path in # production. Destination match is now a hard requirement, not an # additive bonus duration/category can compensate for. urls = ['https://someexplorer.com/turkey-classic-cultural-tour-9-days'] result = trip_finder_service.score_and_rank_urls(urls, 'Peru', 9) assert result == {'url': None, 'confidence': 0, 'found': False} def test_bare_root_domain_url_is_never_a_candidate_even_with_destination_in_domain_name(): # Regression test — a bare root domain could otherwise score high # enough to be a confident "found" match if the destination keyword # happens to appear in the domain itself (e.g. a competitor literally # named "peru-travel.com"), silently presenting a homepage as if it # were a trip-specific page. Root-domain URLs are excluded before # scoring, same as the destination-keyword gate. urls = ['https://www.peru-travel.com/', 'https://www.peru-travel.com'] result = trip_finder_service.score_and_rank_urls(urls, 'Peru', 15) assert result == {'url': None, 'confidence': 0, 'found': False} def test_root_domain_gate_does_not_exclude_urls_with_a_real_path(): # Sanity check — the gate only excludes bare root domains, not # legitimate trip pages that also happen to be on a destination-named # domain. urls = ['https://www.peru-travel.com/', 'https://www.peru-travel.com/classic-15-days'] result = trip_finder_service.score_and_rank_urls(urls, 'Peru', 15) assert result['url'] == 'https://www.peru-travel.com/classic-15-days' assert result['found'] is True def test_find_competitor_trip_url_handles_firecrawl_failure_gracefully(monkeypatch): import requests def _raise(*args, **kwargs): raise requests.RequestException('connection refused') monkeypatch.setattr(trip_finder_service.requests, 'post', _raise) result = trip_finder_service.find_competitor_trip_url('https://intrepidtravel.com', 'Peru', 15, 'Classic') assert result == {'url': None, 'confidence': 0, 'found': False} def test_find_competitor_trip_url_uses_generous_map_timeout(monkeypatch): # Regression test — a large real production site's sitemap.xml fetch # can take well over 15s (confirmed directly: ~21.6s for a real site), # which a too-tight timeout silently turns into a false "no match # found" instead of a real answer. Guards against re-tightening this. captured = {} class _FakeResponse: def raise_for_status(self): pass def json(self): return {'links': []} def _fake_post(url, json, timeout): captured['timeout'] = timeout return _FakeResponse() monkeypatch.setattr(trip_finder_service.requests, 'post', _fake_post) trip_finder_service.find_competitor_trip_url('https://gadventures.com', 'Peru', 15, 'Classic') assert captured['timeout'] == trip_finder_service.FIRECRAWL_MAP_TIMEOUT_SECONDS assert captured['timeout'] >= 30 def test_find_own_trip_url_delegates_to_find_competitor_trip_url(monkeypatch): calls = [] def _fake_find(root_url, destination, duration, travel_style): calls.append((root_url, destination, duration, travel_style)) return {'url': f'{root_url}/peru-classic-15-days', 'confidence': 0.9, 'found': True} monkeypatch.setattr(trip_finder_service, 'find_competitor_trip_url', _fake_find) result = trip_finder_service.find_own_trip_url('https://gadventures.com', 'Peru', 15, 'Classic') assert calls == [('https://gadventures.com', 'Peru', 15, 'Classic')] assert result == {'url': 'https://gadventures.com/peru-classic-15-days', 'confidence': 0.9, 'found': True} def test_find_all_competitor_urls_runs_concurrently_and_preserves_competitor_ids(monkeypatch): def _fake_find(root_url, destination, duration, travel_style): return {'url': f'{root_url}/match', 'confidence': 0.8, 'found': True} monkeypatch.setattr(trip_finder_service, 'find_competitor_trip_url', _fake_find) competitors = [ {'id': 'c1', 'name': 'Intrepid', 'website': 'https://intrepidtravel.com'}, {'id': 'c2', 'name': 'Exodus', 'website': 'https://exodustravels.com'}, ] results = trip_finder_service.find_all_competitor_urls(competitors, 'Peru', 15, 'Classic') assert len(results) == 2 ids = {r['competitor_id'] for r in results} assert ids == {'c1', 'c2'} for r in results: assert r['found'] is True