108 lines
4.5 KiB
Python
108 lines
4.5 KiB
Python
import os
|
|
import sys
|
|
|
|
# Must be set before any repositories module is imported — each module
|
|
# selects its Mock* implementation vs the real PocketBase-backed one at
|
|
# import time based on this flag.
|
|
os.environ['USE_MOCK_REPOSITORIES'] = 'true'
|
|
os.environ.setdefault('API_SECRET_KEY', 'test-api-key')
|
|
os.environ.setdefault('POCKETBASE_URL', 'http://pocketbase.invalid')
|
|
os.environ.setdefault('REDIS_URL', 'redis://localhost:6379/0')
|
|
# Keeps Flask-Limiter off the network entirely in tests — see extensions.py.
|
|
os.environ.setdefault('RATELIMIT_STORAGE_URI', 'memory://')
|
|
os.environ.setdefault('FIRECRAWL_URL', 'http://firecrawl.invalid')
|
|
os.environ.setdefault('GOTIFY_URL', 'http://gotify.invalid')
|
|
os.environ.setdefault('GOTIFY_APP_TOKEN', 'test-token')
|
|
|
|
# Force-cleared (not setdefault) rather than left alone: app.py's
|
|
# load_dotenv() runs on every `client`/`app` fixture use and picks up
|
|
# whatever real credentials happen to be sitting in backend/.env on
|
|
# the machine running the suite — nothing above protects these, since
|
|
# they were never set here before. With real values, SENTRY_DSN
|
|
# actually initialises Sentry and ships real events during test runs,
|
|
# and STRIPE_PRICE_DISCOVER stops being "unset" for the one test that
|
|
# depends on it being absent. Setting them to '' here wins that race —
|
|
# load_dotenv() never overrides an already-present env var, even an
|
|
# empty one — so tests stay hermetic regardless of what's in .env.
|
|
for _leaky_var in (
|
|
'SENTRY_DSN', 'STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET',
|
|
'STRIPE_PRICE_ANALYSE', 'STRIPE_PRICE_DISCOVER', 'STRIPE_PRICE_INTELLIGENCE',
|
|
'RESEND_API_KEY', 'OPENROUTER_API_KEY', 'WEBSHARE_USER', 'WEBSHARE_PASS',
|
|
'BRIGHTDATA_USER', 'BRIGHTDATA_PASS', 'HETZNER_S3_ACCESS_KEY', 'HETZNER_S3_SECRET_KEY',
|
|
'GOOGLE_SERVICE_ACCOUNT_JSON', 'SHEET_ID',
|
|
):
|
|
os.environ[_leaky_var] = ''
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def app():
|
|
from app import app as flask_app
|
|
flask_app.config['TESTING'] = True
|
|
return flask_app
|
|
|
|
|
|
@pytest.fixture
|
|
def client(app):
|
|
return app.test_client()
|
|
|
|
|
|
@pytest.fixture
|
|
def api_headers():
|
|
return {'X-API-Key': 'test-api-key'}
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_rate_limiter():
|
|
"""
|
|
Flask-Limiter's memory:// storage is a module-level singleton
|
|
(extensions.py's `limiter` is created once at import time) — its
|
|
hit counters persist across the whole pytest session, not per
|
|
test. Without this, any route with a tight limit (e.g. "3 per
|
|
day") starts failing tests with 429s once enough tests across the
|
|
whole suite happen to call it, regardless of which test "used up"
|
|
the quota.
|
|
"""
|
|
yield
|
|
from extensions import limiter
|
|
# Only initialised once some test in this session has actually used
|
|
# the `app`/`client` fixture (app.py's limiter.init_app(app)) — pure
|
|
# service-layer tests never touch Flask at all, and limiter.reset()
|
|
# asserts on a storage backend that was never attached.
|
|
if getattr(limiter, '_storage', None) is not None:
|
|
limiter.reset()
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_mock_repositories():
|
|
"""
|
|
Clears every mock repository's in-memory store between tests so
|
|
state doesn't leak across test functions.
|
|
"""
|
|
yield
|
|
from repositories.tenant_repository import tenant_repository
|
|
from repositories.seat_repository import seat_repository
|
|
from repositories.competitor_repository import competitor_repository
|
|
from repositories.product_repository import product_repository
|
|
from repositories.scrape_repository import scrape_repository
|
|
from repositories.proxy_repository import proxy_repository
|
|
from repositories.alert_repository import alert_repository
|
|
from repositories.battlecard_repository import battlecard_repository
|
|
from repositories.comparable_repository import comparable_repository
|
|
from repositories.monitoring_repository import monitoring_repository
|
|
from repositories.price_history_repository import price_history_repository
|
|
from repositories.client_trips_repository import client_trips_repository
|
|
from repositories.ngs_meta_repository import ngs_meta_repository
|
|
|
|
for repo in [
|
|
tenant_repository, seat_repository, competitor_repository, product_repository,
|
|
scrape_repository, proxy_repository, alert_repository, battlecard_repository,
|
|
comparable_repository, monitoring_repository, price_history_repository,
|
|
client_trips_repository, ngs_meta_repository,
|
|
]:
|
|
if hasattr(repo, '_records'):
|
|
repo._records.clear()
|