79 lines
3.2 KiB
Python
79 lines
3.2 KiB
Python
import sys
|
|
import types
|
|
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 now a real Phase 2 module — must use
|
|
# monkeypatch.setattr (not a raw setattr) so the swap reverts after
|
|
# the test instead of permanently corrupting the shared module.
|
|
for name in ('core', 'core.extractor'):
|
|
if name not in sys.modules:
|
|
monkeypatch.setitem(sys.modules, name, types.ModuleType(name))
|
|
monkeypatch.setattr(sys.modules['core.extractor'], '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
|