Files
team-tryouts/tests/conftest.py
T
GGThedandClaude Opus 5 7cec18c139 style: formater le depot avec ruff format
QUA-002, premiere moitie. **Ce commit ne fait que reformater** : aucun
changement de comportement, aucune ligne de logique touchee. 72 fichiers,
4 restaient deja conformes. Il est isole exprès, pour que `git log -p` sur
les commits voisins reste lisible.

`quote-style = "preserve"` etait deja pose dans pyproject.toml, ce qui
evite le brassage guillemets simples / doubles : le diff porte sur les
retours a la ligne, l indentation des appels longs et les virgules
finales, pas sur le style de chaine.

Verification : 263 tests passent avant et apres, ruff check propre.

L activation en CI arrive dans le commit suivant, separement, pour que ce
diff-ci ne contienne rien d autre.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-08 15:53:10 -04:00

195 lines
5.5 KiB
Python

"""Shared pytest fixtures for the Team Tryouts test suite.
The application factory is driven entirely through the ``config`` argument
here: no environment variable is required to run the suite, no database
server is needed, and the Discord bot never starts.
"""
import os
import sys
import tempfile
import pytest
# Make the project root importable as the 'app' package.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import event # noqa: E402
from sqlalchemy.engine import Engine # noqa: E402
from app.app import create_app # noqa: E402
from app.extensions import db as _db # noqa: E402
from app.models import Admin, Coach, Manager, Player, Scout # noqa: E402
@event.listens_for(Engine, 'connect')
def _enforce_sqlite_foreign_keys(dbapi_connection, connection_record):
"""Make SQLite behave like PostgreSQL about foreign keys.
SQLite ignores foreign key constraints unless asked not to. Production
runs on PostgreSQL, which always enforces them, so without this a test
suite could pass over a deletion that fails in production — exactly the
class of bug DATA-004 and DATA-005 turned out to be.
"""
import sqlite3
if isinstance(dbapi_connection, sqlite3.Connection):
cursor = dbapi_connection.cursor()
cursor.execute('PRAGMA foreign_keys=ON')
cursor.close()
ROLE_CLASSES = {
'admin': Admin,
'manager': Manager,
'coach': Coach,
'player': Player,
'scout': Scout,
}
#: Satisfies the documented policy (8+ chars, upper, lower, digit).
VALID_PASSWORD = 'Password123'
def _base_config(db_path, csrf=False):
return {
'SECRET_KEY': 'test-secret-not-used-anywhere-real',
'SQLALCHEMY_DATABASE_URI': f'sqlite:///{db_path}',
'TESTING': True,
'WTF_CSRF_ENABLED': csrf,
# Without these three the suite would 301 every request, start a
# Discord bot, and refuse to issue cookies over the test client.
'FORCE_HTTPS': False,
'SESSION_COOKIE_SECURE': False,
'ENABLE_DISCORD_BOT': False,
# The schema still comes from create_all() until Alembic lands (DB-002).
'AUTO_CREATE_TABLES': True,
'CORS_ALLOWED_ORIGINS': '',
'RATELIMIT_ENABLED': False,
}
@pytest.fixture
def app():
"""A fully configured application backed by a throwaway SQLite file.
A file rather than :memory: because Flask-SQLAlchemy hands out a
connection per thread, and an in-memory database is not shared between
them — tables created on one connection would be invisible to the next.
"""
fd, db_path = tempfile.mkstemp(suffix='.sqlite')
os.close(fd)
application = create_app(_base_config(db_path))
yield application
with application.app_context():
_db.session.remove()
_db.engine.dispose()
try:
os.unlink(db_path)
except OSError:
pass
@pytest.fixture
def app_with_csrf():
"""Same application, with CSRF protection left switched on."""
fd, db_path = tempfile.mkstemp(suffix='.sqlite')
os.close(fd)
application = create_app(_base_config(db_path, csrf=True))
yield application
with application.app_context():
_db.session.remove()
_db.engine.dispose()
try:
os.unlink(db_path)
except OSError:
pass
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture
def db(app):
"""Database handle bound to an active application context."""
with app.app_context():
yield _db
@pytest.fixture
def make_user(app):
"""Factory creating a user of a given role and returning its id.
Returns the primary key rather than the instance: the object would be
detached once the fixture's application context is popped, and every
caller wants to look it up inside its own context anyway.
"""
counter = {'n': 0}
def _make(role='player', password=VALID_PASSWORD, **kwargs):
from app.extensions import hash_password
counter['n'] += 1
n = counter['n']
cls = ROLE_CLASSES[role]
with app.app_context():
user = cls(
username=kwargs.pop('username', f'{role}{n}'),
password_hash=hash_password(password),
role=role,
full_name=kwargs.pop('full_name', f'{role.title()} {n}'),
email=kwargs.pop('email', f'{role}{n}@example.test'),
**kwargs,
)
_db.session.add(user)
_db.session.commit()
return user.id
return _make
@pytest.fixture
def login(client):
"""Log a user in through the real login form.
Deliberately exercises the actual authentication path rather than
poking flask_login's session key, so that session handling itself
stays under test.
"""
def _login(username, password=VALID_PASSWORD):
return client.post(
'/auth/login',
data={'username': username, 'password': password},
follow_redirects=False,
)
return _login
@pytest.fixture
def as_role(app, client, make_user, login):
"""Create a user of the given role, log in, and return its id."""
def _as(role='player', **kwargs):
user_id = make_user(role, **kwargs)
with app.app_context():
from app.models import User
username = _db.session.get(User, user_id).username
response = login(username)
assert response.status_code in (301, 302), (
f'login for {username} did not redirect: {response.status_code}'
)
return user_id
return _as