from services import watchlist_service from repositories.tenant_repository import tenant_repository from repositories.seat_repository import seat_repository from repositories.competitor_repository import competitor_repository from repositories.watchlist_repository import watchlist_repository def _make_tenant_and_seat(tier='analyse'): tenant = tenant_repository.create({ 'name': 'Wild Horizons Travel', 'domain': 'wildhorizons.com', 'tier': tier, 'status': 'active', 'max_seats': 5, }) seat = seat_repository.create({'tenant_id': tenant['id'], 'email': 'pm@wildhorizons.com', 'active': True}) return tenant, seat def _make_competitor(tenant_id): return 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, }) def test_add_to_watchlist_succeeds_under_cap(): tenant, seat = _make_tenant_and_seat() competitor = _make_competitor(tenant['id']) result = watchlist_service.add_to_watchlist( tenant, seat, competitor['id'], 'https://intrepidtravel.com/peru-classic-15-days', 'Peru Classic' ) assert result['success'] is True assert result['item']['competitor_id'] == competitor['id'] assert result['item']['url'] == 'https://intrepidtravel.com/peru-classic-15-days' assert watchlist_repository.count_for_seat(seat['id']) == 1 def test_add_to_watchlist_blocked_at_analyse_cap_of_one(): tenant, seat = _make_tenant_and_seat(tier='analyse') competitor = _make_competitor(tenant['id']) watchlist_service.add_to_watchlist(tenant, seat, competitor['id'], 'https://intrepidtravel.com/peru', 'Peru') result = watchlist_service.add_to_watchlist( tenant, seat, competitor['id'], 'https://intrepidtravel.com/vietnam', 'Vietnam' ) assert result['success'] is False assert '1/1' in result['reason'] assert watchlist_repository.count_for_seat(seat['id']) == 1 # unchanged — never silently overwrites def test_add_to_watchlist_respects_higher_discover_cap(): tenant, seat = _make_tenant_and_seat(tier='discover') competitor = _make_competitor(tenant['id']) first = watchlist_service.add_to_watchlist(tenant, seat, competitor['id'], 'https://intrepidtravel.com/peru', 'Peru') second = watchlist_service.add_to_watchlist(tenant, seat, competitor['id'], 'https://intrepidtravel.com/vietnam', 'Vietnam') third = watchlist_service.add_to_watchlist(tenant, seat, competitor['id'], 'https://intrepidtravel.com/kenya', 'Kenya') assert first['success'] is True assert second['success'] is True assert third['success'] is False # Discover cap is 2/seat def test_add_to_watchlist_defaults_to_cap_of_one_for_unknown_tier(): tenant, seat = _make_tenant_and_seat(tier='unknown-future-tier') competitor = _make_competitor(tenant['id']) first = watchlist_service.add_to_watchlist(tenant, seat, competitor['id'], 'https://intrepidtravel.com/peru', 'Peru') second = watchlist_service.add_to_watchlist(tenant, seat, competitor['id'], 'https://intrepidtravel.com/vietnam', 'Vietnam') assert first['success'] is True assert second['success'] is False def test_two_seats_each_get_their_own_independent_slot(): tenant, seat_a = _make_tenant_and_seat() seat_b = seat_repository.create({'tenant_id': tenant['id'], 'email': 'other@wildhorizons.com', 'active': True}) competitor = _make_competitor(tenant['id']) result_a = watchlist_service.add_to_watchlist(tenant, seat_a, competitor['id'], 'https://intrepidtravel.com/peru', 'Peru') result_b = watchlist_service.add_to_watchlist(tenant, seat_b, competitor['id'], 'https://intrepidtravel.com/vietnam', 'Vietnam') assert result_a['success'] is True assert result_b['success'] is True # seat_a being at cap doesn't block seat_b def test_remove_from_watchlist_frees_the_slot(): tenant, seat = _make_tenant_and_seat() competitor = _make_competitor(tenant['id']) added = watchlist_service.add_to_watchlist(tenant, seat, competitor['id'], 'https://intrepidtravel.com/peru', 'Peru') removed = watchlist_service.remove_from_watchlist(added['item']['id']) assert removed is True assert watchlist_repository.count_for_seat(seat['id']) == 0 second = watchlist_service.add_to_watchlist(tenant, seat, competitor['id'], 'https://intrepidtravel.com/vietnam', 'Vietnam') assert second['success'] is True def test_remove_from_watchlist_returns_false_for_unknown_item(): assert watchlist_service.remove_from_watchlist('does-not-exist') is False def test_list_watchlist_for_tenant_enriches_with_seat_email(): tenant, seat = _make_tenant_and_seat() competitor = _make_competitor(tenant['id']) watchlist_service.add_to_watchlist(tenant, seat, competitor['id'], 'https://intrepidtravel.com/peru', 'Peru Classic') items = watchlist_service.list_watchlist_for_tenant(tenant['id']) assert len(items) == 1 assert items[0]['watched_by_email'] == 'pm@wildhorizons.com' assert items[0]['trip_name'] == 'Peru Classic' def test_list_watchlist_for_tenant_shows_items_from_every_seat(): tenant, seat_a = _make_tenant_and_seat() seat_b = seat_repository.create({'tenant_id': tenant['id'], 'email': 'other@wildhorizons.com', 'active': True}) competitor = _make_competitor(tenant['id']) watchlist_service.add_to_watchlist(tenant, seat_a, competitor['id'], 'https://intrepidtravel.com/peru', 'Peru') watchlist_service.add_to_watchlist(tenant, seat_b, competitor['id'], 'https://intrepidtravel.com/vietnam', 'Vietnam') items = watchlist_service.list_watchlist_for_tenant(tenant['id']) emails = {item['watched_by_email'] for item in items} assert emails == {'pm@wildhorizons.com', 'other@wildhorizons.com'} def test_list_watchlist_for_tenant_handles_removed_seat_gracefully(): tenant, seat = _make_tenant_and_seat() competitor = _make_competitor(tenant['id']) watchlist_service.add_to_watchlist(tenant, seat, competitor['id'], 'https://intrepidtravel.com/peru', 'Peru') seat_repository.delete(seat['id']) # seat later removed from the team items = watchlist_service.list_watchlist_for_tenant(tenant['id']) assert len(items) == 1 assert items[0]['watched_by_email'] is None