Files
shopdb-flask/tests/conftest.py
Cam Proudlock a9d0dfff4a Initial import: shopdb-flask multi-site asset platform
Flask + Vue rewrite of the classic-ASP shopdb, built as a plugin
framework so sister GE Aerospace sites can adopt it. Product version
0.7.0-pre-0.8.0, plugin contract 0.11.0, 966 backend tests.

Key entry points:
- docs/INSTALL-WINDOWS-IIS.md - tested Windows + IIS install runbook
  (own-site and subpath deployment methods)
- docs/PILOT-DEPLOY.md - production pilot: stand up + import + verify
- docs/IMPORT-API.md - schema-agnostic legacy data import contract
- scripts/site_imports/wjf/ - West Jefferson reference import loader
- docs/adr/ - twelve accepted architecture decision records
2026-07-14 07:58:51 -04:00

149 lines
4.3 KiB
Python

"""Pytest configuration and fixtures for shopdb-flask.
Strategy: in-memory SQLite via StaticPool (configured in TestingConfig)
so the database is shared across the connection. Each test drops and
recreates the schema. Simple, totally isolated, fast enough for a small
schema. Switch to savepoint-per-test if test count grows past a few
hundred.
"""
import json
import os
from datetime import datetime, timezone
from pathlib import Path
import pytest
from werkzeug.security import generate_password_hash
# Force testing config before any shopdb import touches the env.
os.environ['FLASK_ENV'] = 'testing'
from shopdb import create_app
from shopdb.extensions import db as _db
def _bootstrap_plugin_registry():
# Fresh clones (CI) have no instance/plugins.json - the registry is
# gitignored dev state - so no plugins would load and every plugin-backed
# test fails. Seed a registry enabling all bundled plugins. An existing
# dev registry is left untouched.
repo = Path(__file__).resolve().parent.parent
state_file = repo / 'instance' / 'plugins.json'
if state_file.exists():
return
stamp = datetime.now(timezone.utc).replace(tzinfo=None).isoformat()
plugins = {}
for manifest_path in sorted((repo / 'plugins').glob('*/manifest.json')):
manifest = json.loads(manifest_path.read_text())
plugins[manifest['name']] = {
'name': manifest['name'],
'version': manifest.get('version', '1.0.0'),
'installed_at': stamp,
'enabled': True,
'migrations_applied': [],
'config': {},
}
state_file.parent.mkdir(parents=True, exist_ok=True)
state_file.write_text(json.dumps({'plugins': plugins}, indent=2))
_bootstrap_plugin_registry()
@pytest.fixture(scope='session')
def app():
"""Create the Flask application for the test session."""
application = create_app('testing')
return application
@pytest.fixture(scope='function')
def db(app):
"""Provide a fresh database per test. Drops and recreates schema each run."""
with app.app_context():
_db.create_all()
yield _db
_db.session.remove()
_db.drop_all()
@pytest.fixture
def client(app):
"""Flask test client."""
return app.test_client()
@pytest.fixture
def runner(app):
"""Flask CLI test runner."""
return app.test_cli_runner()
@pytest.fixture
def admin_user(db):
"""Create an admin user for authenticated tests.
The user has username 'testadmin' and password 'testpass'.
"""
from shopdb.core.models import User, Role
role = Role(rolename='admin', description='Administrator')
db.session.add(role)
db.session.flush()
user = User(
username='testadmin',
email='admin@test.local',
passwordhash=generate_password_hash('testpass'),
)
user.roles.append(role)
db.session.add(user)
db.session.commit()
return user
@pytest.fixture
def auth_headers(client, admin_user):
"""Log in as admin_user and return Authorization headers."""
response = client.post(
'/api/auth/login',
json={'username': 'testadmin', 'password': 'testpass'},
)
assert response.status_code == 200, f'Login failed: {response.get_json()}'
payload = response.get_json()
token = payload['data']['access_token']
return {'Authorization': f'Bearer {token}'}
@pytest.fixture
def member_user(db):
"""Create an authenticated user with NO roles/permissions.
Username 'testmember', password 'testpass'. Used to prove that
authentication alone does not grant write access (authorization gating).
"""
from shopdb.core.models import User
user = User(
username='testmember',
email='member@test.local',
passwordhash=generate_password_hash('testpass'),
)
db.session.add(user)
db.session.commit()
return user
@pytest.fixture
def member_headers(client, member_user):
"""Log in as member_user (no roles) and return Authorization headers."""
response = client.post(
'/api/auth/login',
json={'username': 'testmember', 'password': 'testpass'},
)
assert response.status_code == 200, f'Login failed: {response.get_json()}'
token = response.get_json()['data']['access_token']
return {'Authorization': f'Bearer {token}'}