219 lines
6.7 KiB
Python
219 lines
6.7 KiB
Python
"""
|
|
Pytest fixtures for the JQC test suite.
|
|
|
|
IMPORTANT: config.py evaluates `_require_env('SECRET_KEY')` / `DATABASE_URL` at
|
|
import time, so the required environment variables must be set *before* anything
|
|
imports `app` or `config`. pytest imports this conftest before collecting any
|
|
test module, so setting them at module top here is sufficient — the
|
|
TestingConfig then overrides the DB URI with in-memory SQLite regardless.
|
|
"""
|
|
import os
|
|
|
|
os.environ.setdefault('SECRET_KEY', 'test-secret-key')
|
|
os.environ.setdefault('DATABASE_URL', 'sqlite://') # overridden by TestingConfig
|
|
os.environ.setdefault('DIGEST_SECRET', 'test-digest-secret')
|
|
|
|
from datetime import timedelta
|
|
|
|
import pytest
|
|
|
|
from app import create_app, db as _db
|
|
from app.utils.time_utils import now_eastern
|
|
|
|
# Import model modules so their tables are registered on the metadata before
|
|
# create_all() runs (they are also imported transitively via blueprints).
|
|
from app.models.user import User
|
|
from app.models.project import Project
|
|
from app.models.facility import Facility, Area
|
|
from app.models.inspection import InspectionTemplate, Inspection
|
|
from app.models.issue import Issue
|
|
|
|
|
|
# ── Core fixtures ─────────────────────────────────────────────────────────────
|
|
|
|
@pytest.fixture(scope='session')
|
|
def _app():
|
|
"""Build the Flask app exactly once for the whole session.
|
|
|
|
create_app() cannot be called more than once per process: register_api()
|
|
attaches sub-blueprints to the module-level `api_bp` singleton, and Flask
|
|
forbids re-registering a blueprint. So the app object is session-scoped.
|
|
"""
|
|
return create_app('testing')
|
|
|
|
|
|
@pytest.fixture
|
|
def app(_app):
|
|
"""A fresh app context + empty in-memory schema for each test.
|
|
|
|
A NEW app context is pushed per test (not once per session): Flask-Login
|
|
caches the current user in `g`, which is bound to the app context, so a
|
|
shared context would leak an authenticated user from one test into the next.
|
|
"""
|
|
with _app.app_context():
|
|
_db.create_all()
|
|
try:
|
|
yield _app
|
|
finally:
|
|
_db.session.remove()
|
|
_db.drop_all()
|
|
|
|
|
|
@pytest.fixture
|
|
def db(app):
|
|
"""The active SQLAlchemy session, bound to the per-test app context."""
|
|
return _db
|
|
|
|
|
|
@pytest.fixture
|
|
def client(app):
|
|
"""A Flask test client for the per-test app."""
|
|
return app.test_client()
|
|
|
|
|
|
@pytest.fixture
|
|
def login(client):
|
|
"""Return a helper that logs the given user in via the real login route.
|
|
|
|
Usage: login(user, password='pw')
|
|
CSRF is disabled in TestingConfig, so a plain form POST authenticates.
|
|
"""
|
|
def _login(user, password='pw'):
|
|
# Don't follow the redirect: the session cookie is already set on the
|
|
# 302 response, and this avoids coupling the helper to whatever the
|
|
# post-login landing page renders.
|
|
return client.post(
|
|
'/auth/login',
|
|
data={'username': user.username, 'password': password},
|
|
follow_redirects=False,
|
|
)
|
|
return _login
|
|
|
|
|
|
# ── Model factory helpers ─────────────────────────────────────────────────────
|
|
# Small builders that commit and return a persisted row. Kept deliberately thin;
|
|
# tests pass only the attributes they care about.
|
|
|
|
_counter = {'n': 0}
|
|
|
|
|
|
def _uniq():
|
|
_counter['n'] += 1
|
|
return _counter['n']
|
|
|
|
|
|
@pytest.fixture
|
|
def make_user(db):
|
|
def _make(role='inspector', password='pw', username=None, **kw):
|
|
n = _uniq()
|
|
user = User(
|
|
username=username or f'{role}{n}',
|
|
email=kw.pop('email', f'{role}{n}@example.com'),
|
|
full_name=kw.pop('full_name', None),
|
|
role=role,
|
|
active=kw.pop('active', True),
|
|
)
|
|
user.set_password(password)
|
|
for k, v in kw.items():
|
|
setattr(user, k, v)
|
|
db.session.add(user)
|
|
db.session.commit()
|
|
return user
|
|
return _make
|
|
|
|
|
|
@pytest.fixture
|
|
def make_facility(db):
|
|
def _make(active=True, project=None, with_token=True, **kw):
|
|
n = _uniq()
|
|
fac = Facility(
|
|
name=kw.pop('name', f'Facility {n}'),
|
|
address=kw.pop('address', f'{n} Test St'),
|
|
active=active,
|
|
project_id=project.id if project else None,
|
|
)
|
|
if with_token:
|
|
fac.ensure_public_token()
|
|
for k, v in kw.items():
|
|
setattr(fac, k, v)
|
|
db.session.add(fac)
|
|
db.session.commit()
|
|
return fac
|
|
return _make
|
|
|
|
|
|
@pytest.fixture
|
|
def make_area(db):
|
|
def _make(facility, with_token=True, **kw):
|
|
n = _uniq()
|
|
area = Area(
|
|
facility_id=facility.id,
|
|
name=kw.pop('name', f'Area {n}'),
|
|
area_type=kw.pop('area_type', 'restroom'),
|
|
)
|
|
if with_token:
|
|
area.ensure_public_token()
|
|
for k, v in kw.items():
|
|
setattr(area, k, v)
|
|
db.session.add(area)
|
|
db.session.commit()
|
|
return area
|
|
return _make
|
|
|
|
|
|
@pytest.fixture
|
|
def make_template(db):
|
|
def _make(**kw):
|
|
n = _uniq()
|
|
tpl = InspectionTemplate(
|
|
name=kw.pop('name', f'Template {n}'),
|
|
active=kw.pop('active', True),
|
|
)
|
|
for k, v in kw.items():
|
|
setattr(tpl, k, v)
|
|
db.session.add(tpl)
|
|
db.session.commit()
|
|
return tpl
|
|
return _make
|
|
|
|
|
|
@pytest.fixture
|
|
def make_inspection(db):
|
|
def _make(facility, inspector, template, area=None,
|
|
status='completed', overall_score=90.0, days_ago=5, **kw):
|
|
insp = Inspection(
|
|
template_id=template.id,
|
|
facility_id=facility.id,
|
|
area_id=area.id if area else None,
|
|
inspector_id=inspector.id,
|
|
inspection_date=now_eastern() - timedelta(days=days_ago),
|
|
overall_score=overall_score,
|
|
status=status,
|
|
)
|
|
for k, v in kw.items():
|
|
setattr(insp, k, v)
|
|
db.session.add(insp)
|
|
db.session.commit()
|
|
return insp
|
|
return _make
|
|
|
|
|
|
@pytest.fixture
|
|
def make_issue(db):
|
|
def _make(facility=None, area=None, status='open', severity='medium',
|
|
description='Test issue', **kw):
|
|
issue = Issue(
|
|
facility_id=facility.id if facility else None,
|
|
area_id=area.id if area else None,
|
|
status=status,
|
|
severity=severity,
|
|
description=description,
|
|
reported_at=kw.pop('reported_at', now_eastern()),
|
|
)
|
|
for k, v in kw.items():
|
|
setattr(issue, k, v)
|
|
db.session.add(issue)
|
|
db.session.commit()
|
|
return issue
|
|
return _make
|