297 lines
12 KiB
Python
297 lines
12 KiB
Python
"""
|
|
Every Real*Repository class is a thin wrapper over pb_client. These
|
|
tests instantiate the real classes directly (bypassing the Mock*
|
|
selection that USE_MOCK_REPOSITORIES otherwise activates) and patch
|
|
each module's `pb_client` reference with a recording fake, verifying
|
|
correct collection names and call delegation without needing a live
|
|
PocketBase instance.
|
|
"""
|
|
from repositories.tenant_repository import TenantRepository
|
|
from repositories.seat_repository import SeatRepository
|
|
from repositories.competitor_repository import CompetitorRepository
|
|
from repositories.product_repository import ProductRepository
|
|
from repositories.scrape_repository import ScrapeRepository
|
|
from repositories.proxy_repository import ProxyRepository
|
|
from repositories.alert_repository import AlertRepository
|
|
from repositories.battlecard_repository import BattlecardRepository
|
|
from repositories.comparable_repository import ComparableRepository
|
|
from repositories.brief_repository import BriefRepository
|
|
from repositories.monitoring_repository import MonitoringRepository
|
|
from repositories.price_history_repository import PriceHistoryRepository
|
|
from repositories.client_trips_repository import ClientTripsRepository
|
|
from repositories.watchlist_repository import WatchlistRepository
|
|
|
|
|
|
class _FakePbClient:
|
|
"""Records every call made to it and returns canned values."""
|
|
|
|
def __init__(self):
|
|
self.calls = []
|
|
self.list_return = []
|
|
self.get_first_return = {'id': 'rec1'}
|
|
self.get_one_return = {'id': 'rec1'}
|
|
self.create_return = {'id': 'rec1'}
|
|
self.update_return = {'id': 'rec1', 'updated': True}
|
|
|
|
def list(self, collection, filter_str=None, sort=None, page=1, per_page=50):
|
|
self.calls.append(('list', collection, filter_str, sort))
|
|
return self.list_return
|
|
|
|
def get_first(self, collection, filter_str):
|
|
self.calls.append(('get_first', collection, filter_str))
|
|
return self.get_first_return
|
|
|
|
def get_one(self, collection, record_id):
|
|
self.calls.append(('get_one', collection, record_id))
|
|
return self.get_one_return
|
|
|
|
def create(self, collection, data):
|
|
self.calls.append(('create', collection, data))
|
|
return self.create_return
|
|
|
|
def update(self, collection, record_id, data):
|
|
self.calls.append(('update', collection, record_id, data))
|
|
return self.update_return
|
|
|
|
def delete(self, collection, record_id):
|
|
self.calls.append(('delete', collection, record_id))
|
|
return True
|
|
|
|
|
|
def test_tenant_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
monkeypatch.setattr('repositories.tenant_repository.pb_client', fake)
|
|
repo = TenantRepository()
|
|
|
|
assert repo.get_by_domain('gadventures.com') == fake.get_first_return
|
|
assert repo.get_by_id('t1') == fake.get_one_return
|
|
assert repo.get_by_stripe_customer_id('cus_123') == fake.get_first_return
|
|
assert repo.create({'name': 'x'}) == fake.create_return
|
|
assert repo.update('t1', {'name': 'y'}) == fake.update_return
|
|
assert fake.calls[0] == ('get_first', 'tenants', 'domain = "gadventures.com"')
|
|
|
|
|
|
def test_seat_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = [{'id': 's1'}, {'id': 's2'}]
|
|
monkeypatch.setattr('repositories.seat_repository.pb_client', fake)
|
|
repo = SeatRepository()
|
|
|
|
assert repo.get_active('t1', 'pm@gadventures.com') == fake.get_first_return
|
|
assert repo.get_by_id('s1') == fake.get_one_return
|
|
assert repo.count_active('t1') == 2
|
|
assert repo.list_for_tenant('t1') == fake.list_return
|
|
assert repo.create({'email': 'x'}) == fake.create_return
|
|
repo.update_last_accessed('s1')
|
|
assert repo.delete('s1') is True
|
|
|
|
|
|
def test_competitor_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = [{'id': 'c1', 'website': 'https://intrepidtravel.com'}]
|
|
monkeypatch.setattr('repositories.competitor_repository.pb_client', fake)
|
|
repo = CompetitorRepository()
|
|
|
|
assert repo.get_by_domain('t1', 'intrepidtravel.com') == fake.list_return[0]
|
|
assert repo.get_by_id('c1') == fake.get_one_return
|
|
assert repo.list_for_tenant('t1') == fake.list_return
|
|
assert repo.create({'name': 'x'}) == fake.create_return
|
|
assert repo.update('c1', {'active': False}) == fake.update_return
|
|
|
|
|
|
def test_product_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = [{'id': 'p1', 'last_seen': '2026-01-01'}]
|
|
monkeypatch.setattr('repositories.product_repository.pb_client', fake)
|
|
repo = ProductRepository()
|
|
|
|
assert repo.get_latest_for_competitor('c1') == fake.list_return
|
|
assert repo.list_for_tenant('t1') == fake.list_return
|
|
assert repo.create({'trip_name': 'x'}) == fake.create_return
|
|
assert repo.update('p1', {'majority_price_usd': 100}) == fake.update_return
|
|
assert repo.get_by_competitor_and_name('c1', 'Peru Classic') == fake.get_first_return
|
|
assert repo.get_by_competitor_and_url('c1', 'https://intrepidtravel.com/peru') == fake.get_first_return
|
|
|
|
|
|
def test_product_repository_get_by_competitor_and_name_none_without_trip_name(monkeypatch):
|
|
fake = _FakePbClient()
|
|
monkeypatch.setattr('repositories.product_repository.pb_client', fake)
|
|
repo = ProductRepository()
|
|
|
|
assert repo.get_by_competitor_and_name('c1', None) is None
|
|
assert repo.get_by_competitor_and_name('c1', '') is None
|
|
|
|
|
|
def test_product_repository_get_by_competitor_and_url_none_without_url(monkeypatch):
|
|
fake = _FakePbClient()
|
|
monkeypatch.setattr('repositories.product_repository.pb_client', fake)
|
|
repo = ProductRepository()
|
|
|
|
assert repo.get_by_competitor_and_url('c1', None) is None
|
|
assert repo.get_by_competitor_and_url('c1', '') is None
|
|
|
|
|
|
def test_scrape_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = [{'id': 'r1'}]
|
|
monkeypatch.setattr('repositories.scrape_repository.pb_client', fake)
|
|
repo = ScrapeRepository()
|
|
|
|
assert repo.create({'status': 'pending'}) == fake.create_return
|
|
assert repo.update('r1', {'status': 'complete'}) == fake.update_return
|
|
assert repo.get_by_id('r1') == fake.get_one_return
|
|
assert repo.list_recent_for_tenant('t1') == fake.list_return
|
|
assert repo.get_latest_complete_for_tenant('t1') == fake.list_return[0]
|
|
|
|
|
|
def test_scrape_repository_get_latest_complete_returns_none_when_no_runs(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = []
|
|
monkeypatch.setattr('repositories.scrape_repository.pb_client', fake)
|
|
repo = ScrapeRepository()
|
|
|
|
assert repo.get_latest_complete_for_tenant('t1') is None
|
|
|
|
|
|
def test_scrape_repository_get_last_hash_for_url_returns_hash(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = [{'id': 'r1', 'content_hash': 'abc123'}]
|
|
monkeypatch.setattr('repositories.scrape_repository.pb_client', fake)
|
|
repo = ScrapeRepository()
|
|
|
|
assert repo.get_last_hash_for_url('c1', 'https://intrepidtravel.com/peru') == 'abc123'
|
|
|
|
|
|
def test_scrape_repository_get_last_hash_for_url_returns_none_when_never_hashed(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = []
|
|
monkeypatch.setattr('repositories.scrape_repository.pb_client', fake)
|
|
repo = ScrapeRepository()
|
|
|
|
assert repo.get_last_hash_for_url('c1', 'https://intrepidtravel.com/peru') is None
|
|
|
|
|
|
def test_proxy_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
monkeypatch.setattr('repositories.proxy_repository.pb_client', fake)
|
|
repo = ProxyRepository()
|
|
|
|
assert repo.get_by_domain('intrepidtravel.com') == fake.get_first_return
|
|
assert repo.create({'domain': 'x'}) == fake.create_return
|
|
assert repo.update('o1', {'provider': 'brightdata'}) == fake.update_return
|
|
|
|
|
|
def test_alert_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = [{'id': 'a1'}]
|
|
monkeypatch.setattr('repositories.alert_repository.pb_client', fake)
|
|
repo = AlertRepository()
|
|
|
|
assert repo.create({'alert_type': 'price_change'}) == fake.create_return
|
|
assert repo.list_unread('t1') == fake.list_return
|
|
assert repo.list_recent('t1') == fake.list_return
|
|
assert repo.mark_read('a1') == fake.update_return
|
|
|
|
|
|
def test_battlecard_repository_upsert_creates_when_absent(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.get_first_return = None
|
|
monkeypatch.setattr('repositories.battlecard_repository.pb_client', fake)
|
|
repo = BattlecardRepository()
|
|
|
|
assert repo.get_for_competitor('c1') is None
|
|
result = repo.upsert('c1', {'content': 'x'})
|
|
assert result == fake.create_return
|
|
assert fake.calls[-1][0] == 'create'
|
|
|
|
|
|
def test_battlecard_repository_upsert_updates_when_present(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.get_first_return = {'id': 'b1', 'competitor_id': 'c1'}
|
|
monkeypatch.setattr('repositories.battlecard_repository.pb_client', fake)
|
|
repo = BattlecardRepository()
|
|
|
|
result = repo.upsert('c1', {'content': 'updated'})
|
|
assert result == fake.update_return
|
|
assert fake.calls[-1][0] == 'update'
|
|
assert repo.list_for_tenant('t1') == fake.list_return
|
|
|
|
|
|
def test_comparable_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = [{'id': 'm1'}]
|
|
monkeypatch.setattr('repositories.comparable_repository.pb_client', fake)
|
|
repo = ComparableRepository()
|
|
|
|
assert repo.create({'similarity_score': 0.8}) == fake.create_return
|
|
assert repo.update('m1', {'similarity_score': 0.9}) == fake.update_return
|
|
assert repo.get_by_client_and_competitor_product('t1', 'Inca Trail', 'p1') == fake.get_first_return
|
|
assert repo.list_for_tenant('t1') == fake.list_return
|
|
assert repo.dismiss('m1') == fake.update_return
|
|
|
|
|
|
def test_client_trips_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = [{'id': 'ct1'}]
|
|
monkeypatch.setattr('repositories.client_trips_repository.pb_client', fake)
|
|
repo = ClientTripsRepository()
|
|
|
|
assert repo.create({'trip_name': 'Inca Trail'}) == fake.create_return
|
|
assert repo.update('ct1', {'destination': 'Peru'}) == fake.update_return
|
|
assert repo.get_by_tenant_and_name('t1', 'Inca Trail') == fake.get_first_return
|
|
assert repo.list_for_tenant('t1') == fake.list_return
|
|
|
|
|
|
def test_brief_repository_get_weekly_data_aggregates_collections(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.get_one_return = {'email': 'pm@gadventures.com', 'name': 'G Adventures'}
|
|
fake.list_return = [{'id': 'x'}]
|
|
monkeypatch.setattr('repositories.brief_repository.pb_client', fake)
|
|
repo = BriefRepository()
|
|
|
|
data = repo.get_weekly_data('t1')
|
|
|
|
assert data['admin_email'] == 'pm@gadventures.com'
|
|
assert data['tenant_name'] == 'G Adventures'
|
|
assert data['price_changes'] == [{'id': 'x'}]
|
|
assert data['new_products'] == [{'id': 'x'}]
|
|
assert data['battlecard_updates'] == [{'id': 'x'}]
|
|
|
|
log = repo.log_digest('t1', 'weekly', recipient_email='pm@gadventures.com')
|
|
assert log == fake.create_return
|
|
|
|
|
|
def test_monitoring_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
monkeypatch.setattr('repositories.monitoring_repository.pb_client', fake)
|
|
repo = MonitoringRepository()
|
|
|
|
result = repo.log_event('queue_depth', 'warning', value=6, threshold=5, message='busy', alert_sent=True)
|
|
assert result == fake.create_return
|
|
assert fake.calls[0][1] == 'monitoring_events'
|
|
|
|
|
|
def test_price_history_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = [{'id': 'ph1'}]
|
|
monkeypatch.setattr('repositories.price_history_repository.pb_client', fake)
|
|
repo = PriceHistoryRepository()
|
|
|
|
assert repo.create({'majority_price_usd': 2190}) == fake.create_return
|
|
assert repo.list_for_product('p1') == fake.list_return
|
|
assert fake.calls[-1][1] == 'price_history'
|
|
|
|
|
|
def test_watchlist_repository_delegates_correctly(monkeypatch):
|
|
fake = _FakePbClient()
|
|
fake.list_return = [{'id': 'w1'}, {'id': 'w2'}]
|
|
monkeypatch.setattr('repositories.watchlist_repository.pb_client', fake)
|
|
repo = WatchlistRepository()
|
|
|
|
assert repo.create({'seat_id': 's1'}) == fake.create_return
|
|
assert repo.get_by_id('w1') == fake.get_one_return
|
|
assert repo.list_for_tenant('t1') == fake.list_return
|
|
assert repo.count_for_seat('s1') == 2
|
|
assert repo.delete('w1') is True
|
|
assert fake.calls[-1] == ('delete', 'trip_watchlist', 'w1')
|