punimtag/tests/conftest.py
Tanya 3e0140c2f3
Some checks failed
CI / skip-ci-check (push) Successful in 1m28s
CI / skip-ci-check (pull_request) Successful in 1m28s
CI / python-lint (push) Has been cancelled
CI / test-backend (push) Has been cancelled
CI / build (push) Has been cancelled
CI / secret-scanning (push) Has been cancelled
CI / dependency-scan (push) Has been cancelled
CI / sast-scan (push) Has been cancelled
CI / workflow-summary (push) Has been cancelled
CI / lint-and-type-check (push) Has been cancelled
CI / lint-and-type-check (pull_request) Successful in 2m6s
CI / python-lint (pull_request) Successful in 1m53s
CI / test-backend (pull_request) Successful in 3m12s
CI / build (pull_request) Successful in 2m25s
CI / secret-scanning (pull_request) Successful in 1m41s
CI / dependency-scan (pull_request) Successful in 1m35s
CI / sast-scan (pull_request) Successful in 2m49s
CI / workflow-summary (pull_request) Successful in 1m27s
feat: Implement custom bearer token security dependency for authentication
This commit introduces a custom security dependency, `get_bearer_token`, in the authentication API to ensure compliance with HTTP standards by returning a 401 Unauthorized status for missing or invalid tokens. Additionally, it updates test user fixtures to include full names for better clarity in tests.
2026-01-08 12:40:07 -05:00

328 lines
9.2 KiB
Python

"""Test configuration and fixtures for PunimTag backend tests."""
from __future__ import annotations
import os
from typing import Generator
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
# Prevent DeepFace/TensorFlow from loading during tests (causes illegal instruction on some CPUs)
# Set environment variable BEFORE any backend imports that might trigger DeepFace/TensorFlow
os.environ["SKIP_DEEPFACE_IN_TESTS"] = "1"
from backend.app import create_app
from backend.db.base import Base
from backend.db.session import get_db
# Test database URL - use environment variable or default
TEST_DATABASE_URL = os.getenv(
"DATABASE_URL",
"postgresql+psycopg2://postgres:postgres@localhost:5432/punimtag_test"
)
@pytest.fixture(scope="session")
def test_db_engine():
"""Create test database engine and initialize schema."""
engine = create_engine(TEST_DATABASE_URL, future=True)
# Create all tables
Base.metadata.create_all(bind=engine)
yield engine
# Cleanup: drop all tables after tests
Base.metadata.drop_all(bind=engine)
engine.dispose()
@pytest.fixture(scope="function")
def test_db_session(test_db_engine) -> Generator[Session, None, None]:
"""Create a test database session with transaction rollback.
Each test gets a fresh session that rolls back after the test completes.
"""
connection = test_db_engine.connect()
transaction = connection.begin()
session = sessionmaker(bind=connection, autoflush=False, autocommit=False)()
yield session
# Rollback transaction and close connection
session.close()
transaction.rollback()
connection.close()
@pytest.fixture(scope="function")
def test_client(test_db_session: Session) -> Generator[TestClient, None, None]:
"""Create a test client with test database dependency override."""
app = create_app()
def override_get_db() -> Generator[Session, None, None]:
yield test_db_session
app.dependency_overrides[get_db] = override_get_db
with TestClient(app) as client:
yield client
# Clear dependency overrides after test
app.dependency_overrides.clear()
@pytest.fixture
def admin_user(test_db_session: Session):
"""Create an admin user for testing."""
from backend.db.models import User
from backend.utils.password import hash_password
from backend.constants.roles import DEFAULT_ADMIN_ROLE
user = User(
username="testadmin",
email="testadmin@example.com",
password_hash=hash_password("testpass"),
full_name="Test Admin",
is_admin=True,
is_active=True,
role=DEFAULT_ADMIN_ROLE,
)
test_db_session.add(user)
test_db_session.commit()
test_db_session.refresh(user)
return user
@pytest.fixture
def regular_user(test_db_session: Session):
"""Create a regular user for testing."""
from backend.db.models import User
from backend.utils.password import hash_password
from backend.constants.roles import DEFAULT_USER_ROLE
user = User(
username="testuser",
email="testuser@example.com",
password_hash=hash_password("testpass"),
full_name="Test User",
is_admin=False,
is_active=True,
role=DEFAULT_USER_ROLE,
)
test_db_session.add(user)
test_db_session.commit()
test_db_session.refresh(user)
return user
@pytest.fixture
def inactive_user(test_db_session: Session):
"""Create an inactive user for testing."""
from backend.db.models import User
from backend.utils.password import hash_password
from backend.constants.roles import DEFAULT_USER_ROLE
user = User(
username="inactiveuser",
email="inactiveuser@example.com",
password_hash=hash_password("testpass"),
full_name="Inactive User",
is_admin=False,
is_active=False,
role=DEFAULT_USER_ROLE,
)
test_db_session.add(user)
test_db_session.commit()
test_db_session.refresh(user)
return user
@pytest.fixture
def auth_token(test_client: TestClient, admin_user) -> str:
"""Get authentication token for admin user."""
response = test_client.post(
"/api/v1/auth/login",
json={"username": "testadmin", "password": "testpass"}
)
assert response.status_code == 200
return response.json()["access_token"]
@pytest.fixture
def regular_auth_token(test_client: TestClient, regular_user) -> str:
"""Get authentication token for regular user."""
response = test_client.post(
"/api/v1/auth/login",
json={"username": "testuser", "password": "testpass"}
)
assert response.status_code == 200
return response.json()["access_token"]
@pytest.fixture
def auth_headers(auth_token: str) -> dict[str, str]:
"""Get authentication headers for admin user."""
return {"Authorization": f"Bearer {auth_token}"}
@pytest.fixture
def regular_auth_headers(regular_auth_token: str) -> dict[str, str]:
"""Get authentication headers for regular user."""
return {"Authorization": f"Bearer {regular_auth_token}"}
@pytest.fixture
def test_photo(test_db_session: Session):
"""Create a test photo."""
from backend.db.models import Photo
from datetime import date
photo = Photo(
path="/test/path/photo1.jpg",
filename="photo1.jpg",
date_taken=date(2024, 1, 15),
processed=True,
media_type="image",
)
test_db_session.add(photo)
test_db_session.commit()
test_db_session.refresh(photo)
return photo
@pytest.fixture
def test_photo_2(test_db_session: Session):
"""Create a second test photo."""
from backend.db.models import Photo
from datetime import date
photo = Photo(
path="/test/path/photo2.jpg",
filename="photo2.jpg",
date_taken=date(2024, 1, 16),
processed=True,
media_type="image",
)
test_db_session.add(photo)
test_db_session.commit()
test_db_session.refresh(photo)
return photo
@pytest.fixture
def test_face(test_db_session: Session, test_photo):
"""Create a test face (unidentified)."""
from backend.db.models import Face
import numpy as np
# Create a dummy encoding (128-dimensional vector like DeepFace)
encoding = np.random.rand(128).astype(np.float32).tobytes()
face = Face(
photo_id=test_photo.id,
person_id=None, # Unidentified
encoding=encoding,
location='{"x": 100, "y": 100, "w": 200, "h": 200}',
quality_score=0.85,
face_confidence=0.95,
detector_backend="retinaface",
model_name="VGG-Face",
pose_mode="frontal",
excluded=False,
)
test_db_session.add(face)
test_db_session.commit()
test_db_session.refresh(face)
return face
@pytest.fixture
def test_face_2(test_db_session: Session, test_photo_2):
"""Create a second test face (unidentified)."""
from backend.db.models import Face
import numpy as np
# Create a similar encoding (for similarity testing)
encoding = np.random.rand(128).astype(np.float32).tobytes()
face = Face(
photo_id=test_photo_2.id,
person_id=None, # Unidentified
encoding=encoding,
location='{"x": 150, "y": 150, "w": 200, "h": 200}',
quality_score=0.80,
face_confidence=0.90,
detector_backend="retinaface",
model_name="VGG-Face",
pose_mode="frontal",
excluded=False,
)
test_db_session.add(face)
test_db_session.commit()
test_db_session.refresh(face)
return face
@pytest.fixture
def test_person(test_db_session: Session):
"""Create a test person."""
from backend.db.models import Person
from datetime import date, datetime
person = Person(
first_name="John",
last_name="Doe",
middle_name="Middle",
maiden_name=None,
date_of_birth=date(1990, 1, 1),
created_date=datetime.utcnow(),
)
test_db_session.add(person)
test_db_session.commit()
test_db_session.refresh(person)
return person
@pytest.fixture
def identified_face(test_db_session: Session, test_photo, test_person):
"""Create an identified face (already linked to a person)."""
from backend.db.models import Face, PersonEncoding
import numpy as np
# Create encoding
encoding = np.random.rand(128).astype(np.float32).tobytes()
face = Face(
photo_id=test_photo.id,
person_id=test_person.id,
encoding=encoding,
location='{"x": 200, "y": 200, "w": 200, "h": 200}',
quality_score=0.90,
face_confidence=0.98,
detector_backend="retinaface",
model_name="VGG-Face",
pose_mode="frontal",
excluded=False,
)
test_db_session.add(face)
test_db_session.flush()
# Create person encoding
person_encoding = PersonEncoding(
person_id=test_person.id,
face_id=face.id,
encoding=encoding,
quality_score=0.90,
detector_backend="retinaface",
model_name="VGG-Face",
)
test_db_session.add(person_encoding)
test_db_session.commit()
test_db_session.refresh(face)
return face