app.bettersight.io/backend/tests/services/test_battlecard_service.py

77 lines
3.1 KiB
Python

import importlib
from services import battlecard_service
from repositories.tenant_repository import tenant_repository
from repositories.competitor_repository import competitor_repository
from repositories.product_repository import product_repository
from repositories.battlecard_repository import battlecard_repository
def _install_fake_extractor(monkeypatch, fn):
# core.extractor is a real Phase 2 module — import it for real (not
# a blank fake substitute) and monkeypatch.setattr the one function
# this test needs, so the swap reverts after the test instead of
# permanently corrupting the shared module.
target = importlib.import_module('core.extractor')
monkeypatch.setattr(target, 'extract_with_openrouter', fn)
def _setup():
tenant = tenant_repository.create({
'name': 'G Adventures', 'domain': 'gadventures.com', 'tier': 'analyse',
'status': 'active', 'max_seats': 5,
})
competitor = competitor_repository.create({
'tenant_id': tenant['id'], 'name': 'Intrepid Travel', 'website': 'https://intrepidtravel.com',
'catalogue_url': 'https://intrepidtravel.com/adventures', 'primary_market': 'GB', 'active': True,
})
product_repository.create({
'tenant_id': tenant['id'], 'competitor_id': competitor['id'], 'trip_name': 'Peru Classic',
'majority_price_usd': 2190, 'duration_days': 15,
})
return tenant, competitor
def test_generate_battlecard_creates_record(monkeypatch):
tenant, competitor = _setup()
_install_fake_extractor(monkeypatch, lambda prompt, content: {
'positioning': 'Budget-mid, group focused',
'strengths': ['Strong brand'], 'weaknesses': ['Basic accommodation'],
'pricing_observation': 'Discounting Q3',
})
card = battlecard_service.generate_battlecard(competitor['id'], tenant['id'])
assert card is not None
assert card['content_json']['positioning'] == 'Budget-mid, group focused'
assert 'Strong brand' in card['content']
assert battlecard_repository.get_for_competitor(competitor['id']) is not None
def test_generate_battlecard_returns_none_on_extraction_failure(monkeypatch):
tenant, competitor = _setup()
def _raise(prompt, content):
raise RuntimeError('all models exhausted')
_install_fake_extractor(monkeypatch, _raise)
result = battlecard_service.generate_battlecard(competitor['id'], tenant['id'])
assert result is None
def test_generate_battlecard_handles_no_products_gracefully(monkeypatch):
tenant = tenant_repository.create({
'name': 'G Adventures', 'domain': 'gadventures.com', 'tier': 'analyse',
'status': 'active', 'max_seats': 5,
})
competitor = competitor_repository.create({
'tenant_id': tenant['id'], 'name': 'New Competitor', 'website': 'https://new.com',
'catalogue_url': 'https://new.com/trips', 'primary_market': 'US', 'active': True,
})
_install_fake_extractor(monkeypatch, lambda prompt, content: {
'positioning': 'Unknown', 'strengths': [], 'weaknesses': [], 'pricing_observation': 'n/a',
})
card = battlecard_service.generate_battlecard(competitor['id'], tenant['id'])
assert card is not None