kanban-app/backend/tests/conftest.py

259 lines
6.5 KiB
Python
Raw Normal View History

2026-02-24 14:36:31 +00:00
"""Pytest configuration and fixtures"""
2026-02-25 18:32:57 +00:00
import logging
import time
2026-03-20 17:17:01 +00:00
from io import BytesIO
2026-02-24 16:19:15 +00:00
import pytest
2026-02-24 14:36:31 +00:00
from faker import Faker
2026-03-20 17:17:01 +00:00
from PIL import Image
2026-02-24 14:36:31 +00:00
from app import create_app, db
from app.models import Board, Card, Comment, List, User
2026-02-24 14:36:31 +00:00
2026-02-25 18:32:57 +00:00
log = logging.getLogger(__name__)
2026-02-24 14:36:31 +00:00
fake = Faker()
2026-05-01 15:03:00 +00:00
@pytest.fixture(scope="session")
2026-02-24 14:36:31 +00:00
def app():
2026-02-25 18:32:57 +00:00
"""Create application for testing with PostgreSQL database (session scope)"""
2026-02-24 16:19:15 +00:00
app = create_app(config_name="test")
app.config.update(
{
"TESTING": True,
2026-02-25 18:32:57 +00:00
# fmt: off
2026-02-24 16:19:15 +00:00
"WTF_CSRF_ENABLED": False,
2026-02-25 18:32:57 +00:00
"JWT_SECRET_KEY":
"test-secret-keytest-secret-keytest-secret-keytest-secret-key",
2026-02-24 16:19:15 +00:00
"SERVER_NAME": "localhost.localdomain",
2026-02-25 18:32:57 +00:00
# fmt: on
2026-02-24 16:19:15 +00:00
}
)
2026-02-24 14:36:31 +00:00
2026-02-25 18:32:57 +00:00
# Create tables once per session
2026-02-24 14:36:31 +00:00
with app.app_context():
2026-02-27 12:41:44 +00:00
print("--------db.create_all()------")
2026-02-24 14:36:31 +00:00
db.create_all()
yield app
2026-02-25 18:32:57 +00:00
# Cleanup after all tests
2026-02-24 14:36:31 +00:00
db.session.remove()
db.drop_all()
2026-02-27 20:26:25 +00:00
db.engine.dispose()
2026-02-24 14:36:31 +00:00
@pytest.fixture
def client(app):
"""Test client for making requests"""
2026-02-25 18:32:57 +00:00
log.debug("Test finished - session dirty: %s", db.session.dirty)
log.debug("Test finished - session new: %s", db.session.new)
2026-02-24 14:36:31 +00:00
return app.test_client()
2026-02-25 18:32:57 +00:00
@pytest.fixture(autouse=True)
def _cleanup_db_after_test(app):
"""
Automatically rollback and remove DB session after EACH test function.
- Runs after every test function (default scope='function')
- Depends on 'app' to ensure app context is available
- Rollback prevents data leakage between tests
- remove() returns connection to pool (prevents exhaustion)
"""
yield # Let the test run
with app.app_context():
db.session.rollback() # Undo all changes from the test
db.session.remove() # Return connection to pool
2026-02-24 14:36:31 +00:00
@pytest.fixture
def runner(app):
"""Test CLI runner"""
return app.test_cli_runner()
2026-02-25 18:32:57 +00:00
@pytest.fixture(autouse=True)
def _rollback_db_session(app):
"""Automatically rollback db.session after each test for isolation.
This fixture uses the shared Flask-SQLAlchemy session but ensures
tests don't interfere with each other by rolling back after each test.
"""
yield
with app.app_context():
db.session.rollback()
db.session.remove()
2026-02-24 14:36:31 +00:00
@pytest.fixture
def db_session(app):
2026-02-25 18:32:57 +00:00
"""Database session for tests (function scope)"""
"""Provide the shared Flask-SQLAlchemy db.session for tests"""
2026-02-24 14:36:31 +00:00
with app.app_context():
yield db.session
2026-02-25 18:32:57 +00:00
2026-02-24 14:36:31 +00:00
@pytest.fixture
def admin_user(db_session):
"""Create an admin user for testing"""
user = User(
email=fake.email(),
username=fake.user_name(),
first_name=fake.first_name(),
last_name=fake.last_name(),
is_admin=True,
2026-02-24 16:19:15 +00:00
is_active=True,
2026-02-24 14:36:31 +00:00
)
2026-02-24 16:19:15 +00:00
user.set_password("password123")
2026-02-24 14:36:31 +00:00
db_session.add(user)
db_session.commit()
return user
@pytest.fixture
def regular_user(db_session):
"""Create a regular user for testing"""
user = User(
email=fake.email(),
username=f"{fake.user_name()}_{int(time.time() * 1000)}",
2026-02-24 14:36:31 +00:00
first_name=fake.first_name(),
last_name=fake.last_name(),
is_admin=False,
2026-02-24 16:19:15 +00:00
is_active=True,
2026-02-24 14:36:31 +00:00
)
2026-02-24 16:19:15 +00:00
user.set_password("password123")
2026-02-24 14:36:31 +00:00
db_session.add(user)
db_session.commit()
return user
@pytest.fixture
def inactive_user(db_session):
"""Create an inactive user for testing"""
user = User(
email=fake.email(),
username=fake.user_name(),
first_name=fake.first_name(),
last_name=fake.last_name(),
is_admin=False,
2026-02-24 16:19:15 +00:00
is_active=False,
2026-02-24 14:36:31 +00:00
)
2026-02-24 16:19:15 +00:00
user.set_password("password123")
2026-02-24 14:36:31 +00:00
db_session.add(user)
db_session.commit()
return user
@pytest.fixture
def auth_headers(client, regular_user):
"""Get authentication headers for a regular user"""
2026-02-24 16:19:15 +00:00
response = client.post(
"/api/auth/login", json={"email": regular_user.email, "password": "password123"}
)
2026-02-24 14:36:31 +00:00
data = response.get_json()
2026-02-25 18:32:57 +00:00
response.close()
2026-02-24 16:19:15 +00:00
token = data["access_token"]
2026-02-24 14:36:31 +00:00
print(f"Auth headers token for user {regular_user.email}: {token[:50]}...")
2026-02-24 16:19:15 +00:00
return {"Authorization": f"Bearer {token}"}
2026-02-24 14:36:31 +00:00
@pytest.fixture
def admin_headers(client, admin_user):
"""Get authentication headers for an admin user"""
2026-02-24 16:19:15 +00:00
response = client.post(
"/api/auth/login", json={"email": admin_user.email, "password": "password123"}
)
2026-02-24 14:36:31 +00:00
data = response.get_json()
2026-02-24 16:19:15 +00:00
token = data["access_token"]
2026-02-24 14:36:31 +00:00
print(f"Admin headers token for user {admin_user.email}: {token[:50]}...")
2026-02-24 16:19:15 +00:00
return {"Authorization": f"Bearer {token}"}
2026-02-24 14:36:31 +00:00
2026-03-20 17:17:01 +00:00
# ============ File Upload Fixtures ============
@pytest.fixture
def test_image_file():
"""Create a test image file as BytesIO"""
# Create a simple red PNG image using PIL
img = Image.new("RGB", (10, 10), color="red")
img_io = BytesIO()
img.save(img_io, format="PNG")
img_io.seek(0)
return img_io
@pytest.fixture
def test_large_image_file():
"""Create a larger test image (50KB)"""
# Create a larger image using PIL
img = Image.new("RGB", (200, 200), color="blue")
img_io = BytesIO()
img.save(img_io, format="PNG")
img_io.seek(0)
return img_io
@pytest.fixture
def test_pdf_file():
"""Create a test PDF file as BytesIO"""
# Minimal valid PDF
pdf_data = b"%PDF-1.4\n1 0 obj\n<<\n/Type /Catalog\n>>\nendobj\n%%EOF"
return BytesIO(pdf_data)
@pytest.fixture
def test_board(db_session, regular_user):
"""Create a test board"""
board = Board(
user_id=regular_user.id,
name=fake.sentence(nb_words=4)[:-1],
description=fake.paragraph(),
)
db_session.add(board)
db_session.commit()
return board
@pytest.fixture
def test_list(db_session, test_board):
"""Create a test list"""
lst = List(
board_id=test_board.id,
name=fake.sentence(nb_words=3)[:-1],
pos=0,
)
db_session.add(lst)
db_session.commit()
return lst
@pytest.fixture
def test_card(db_session, test_list):
"""Create a test card"""
card = Card(
list_id=test_list.id,
name=fake.sentence(nb_words=4)[:-1],
description=fake.paragraph(),
pos=0,
due=None,
board_id=test_list.board_id,
)
db_session.add(card)
db_session.commit()
return card
@pytest.fixture
def test_comment(db_session, test_card, regular_user):
"""Create a test comment"""
comment = Comment(
card_id=test_card.id,
user_id=regular_user.id,
text=fake.paragraph(),
)
db_session.add(comment)
db_session.commit()
return comment