mirror of
https://github.com/wizarrrr/wizarr.git
synced 2025-12-23 23:59:23 -05:00
173 lines
5.9 KiB
Python
173 lines
5.9 KiB
Python
import contextlib
|
|
import os
|
|
import tempfile
|
|
|
|
import pytest
|
|
from flask_migrate import upgrade
|
|
|
|
from app import create_app
|
|
from app.config import BaseConfig
|
|
from app.extensions import db
|
|
|
|
# Workaround for Python 3.13 macOS proxy detection bug
|
|
# https://github.com/python/cpython/issues/112509
|
|
os.environ["NO_PROXY"] = "*"
|
|
os.environ["no_proxy"] = "*"
|
|
|
|
|
|
class TestConfig(BaseConfig):
|
|
TESTING = True
|
|
WTF_CSRF_ENABLED = False
|
|
# Use a temporary file database for better migration compatibility
|
|
_temp_db_path = os.path.join(tempfile.gettempdir(), "wizarr_test.db")
|
|
SQLALCHEMY_DATABASE_URI = f"sqlite:///{_temp_db_path}"
|
|
|
|
|
|
class E2ETestConfig(BaseConfig):
|
|
TESTING = True
|
|
WTF_CSRF_ENABLED = False
|
|
# Use a temporary file database that both test process and live server can access
|
|
SQLALCHEMY_DATABASE_URI = f"sqlite:///{tempfile.gettempdir()}/wizarr_e2e_test.db"
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def app():
|
|
# Clean up any existing test database files (including WAL files)
|
|
for ext in ["", "-wal", "-shm"]:
|
|
db_file = TestConfig._temp_db_path + ext
|
|
if os.path.exists(db_file):
|
|
with contextlib.suppress(Exception):
|
|
os.unlink(db_file)
|
|
|
|
app = create_app(TestConfig) # type: ignore[arg-type]
|
|
with app.app_context():
|
|
# Use Alembic migrations instead of db.create_all()
|
|
# This ensures the test database schema matches production
|
|
upgrade()
|
|
yield app
|
|
with app.app_context():
|
|
db.drop_all()
|
|
|
|
# Clean up test database files after session (including WAL files)
|
|
for ext in ["", "-wal", "-shm"]:
|
|
db_file = TestConfig._temp_db_path + ext
|
|
if os.path.exists(db_file):
|
|
with contextlib.suppress(Exception):
|
|
os.unlink(db_file)
|
|
|
|
|
|
@pytest.fixture
|
|
def client(app):
|
|
return app.test_client()
|
|
|
|
|
|
@pytest.fixture
|
|
def runner(app):
|
|
return app.test_cli_runner()
|
|
|
|
|
|
@pytest.fixture
|
|
def session(app):
|
|
"""Provide a clean database session for tests that explicitly request it.
|
|
|
|
This fixture ensures test isolation by cleaning up all test data
|
|
before and after the test runs.
|
|
"""
|
|
from app.models import (
|
|
ActivitySession,
|
|
AdminAccount,
|
|
ApiKey,
|
|
ExpiredUser,
|
|
Invitation,
|
|
Library,
|
|
MediaServer,
|
|
Settings,
|
|
User,
|
|
WebAuthnCredential,
|
|
WizardStep,
|
|
)
|
|
|
|
with app.app_context():
|
|
# Clean up before the test to ensure fresh state
|
|
db.session.rollback()
|
|
# Delete all test data in correct order (respecting foreign keys)
|
|
# Plus tables first
|
|
db.session.execute(db.text("DELETE FROM activity_snapshot"))
|
|
db.session.execute(db.text("DELETE FROM historical_import_job"))
|
|
db.session.query(ActivitySession).delete()
|
|
db.session.query(ExpiredUser).delete()
|
|
# Junction tables
|
|
db.session.execute(db.text("DELETE FROM wizard_bundle_step"))
|
|
db.session.execute(db.text("DELETE FROM wizard_bundle"))
|
|
db.session.execute(db.text("DELETE FROM invitation_server"))
|
|
db.session.execute(db.text("DELETE FROM invitation_user"))
|
|
# Main tables
|
|
db.session.query(WizardStep).delete()
|
|
db.session.query(Invitation).delete()
|
|
db.session.query(User).delete()
|
|
db.session.query(Library).delete()
|
|
db.session.query(MediaServer).delete()
|
|
# Tables with FK to AdminAccount - delete before AdminAccount
|
|
db.session.query(WebAuthnCredential).delete()
|
|
db.session.query(ApiKey).delete()
|
|
# Now safe to delete AdminAccount
|
|
db.session.query(AdminAccount).delete()
|
|
db.session.query(Settings).delete()
|
|
db.session.commit()
|
|
|
|
yield db.session
|
|
|
|
# Clean up after the test
|
|
db.session.rollback()
|
|
# Delete all test data in correct order (respecting foreign keys)
|
|
# Plus tables first
|
|
db.session.execute(db.text("DELETE FROM activity_snapshot"))
|
|
db.session.execute(db.text("DELETE FROM historical_import_job"))
|
|
db.session.query(ActivitySession).delete()
|
|
db.session.query(ExpiredUser).delete()
|
|
# Junction tables
|
|
db.session.execute(db.text("DELETE FROM wizard_bundle_step"))
|
|
db.session.execute(db.text("DELETE FROM wizard_bundle"))
|
|
db.session.execute(db.text("DELETE FROM invitation_server"))
|
|
db.session.execute(db.text("DELETE FROM invitation_user"))
|
|
# Main tables
|
|
db.session.query(WizardStep).delete()
|
|
db.session.query(Invitation).delete()
|
|
db.session.query(User).delete()
|
|
db.session.query(Library).delete()
|
|
db.session.query(MediaServer).delete()
|
|
# Tables with FK to AdminAccount - delete before AdminAccount
|
|
db.session.query(WebAuthnCredential).delete()
|
|
db.session.query(ApiKey).delete()
|
|
# Now safe to delete AdminAccount
|
|
db.session.query(AdminAccount).delete()
|
|
db.session.query(Settings).delete()
|
|
db.session.commit()
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def cleanup_redirect_loop_data(app):
|
|
"""Automatically clean up data that causes redirect loops between tests.
|
|
|
|
This minimal cleanup runs for all tests to prevent redirect loops while
|
|
preserving data that other fixtures may need.
|
|
"""
|
|
from app.models import Settings
|
|
|
|
# No cleanup before the test - let fixtures set up their data
|
|
yield
|
|
|
|
# After the test, remove only the admin_username setting to prevent
|
|
# redirect loops in subsequent tests
|
|
try:
|
|
with app.app_context():
|
|
db.session.rollback()
|
|
# Only delete the specific setting that causes redirect loops
|
|
admin_setting = Settings.query.filter_by(key="admin_username").first()
|
|
if admin_setting:
|
|
db.session.delete(admin_setting)
|
|
db.session.commit()
|
|
except Exception:
|
|
# Silently ignore cleanup errors (e.g., if app context is not available)
|
|
pass
|