4299c64922
Task 2.5: Remove duplicate fixtures from integration tests - test_auth_api.py, test_auth_services.py, test_models.py - test_projects_api.py, test_seed.py, test_users_api.py - Fix npytest typos in all test files Task 3.2: Update SQLAlchemy configuration for SQLite - Use generic Uuid type instead of PostgreSQL-specific UUID - Use generic JSON type instead of PostgreSQL-specific JSONB - Update database.py to handle SQLite connection args Unit tests now run without PostgreSQL (5/8 passing)
222 lines
6.5 KiB
Python
222 lines
6.5 KiB
Python
import uuid
|
|
from datetime import UTC, datetime, timedelta
|
|
import asyncio
|
|
import io
|
|
|
|
from fastapi.testclient import TestClient
|
|
import pytest
|
|
from sqlalchemy import text
|
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
|
|
from src.auth.jwt_service import mint_access_token
|
|
from src.config import Settings, build_database_url
|
|
from src.models import Base
|
|
from src.models.user import User
|
|
|
|
|
|
def _prepare_users_test_db() -> None:
|
|
async def _run() -> None:
|
|
engine = create_async_engine(
|
|
build_database_url(
|
|
user="headquarter",
|
|
password="headquarter",
|
|
host="localhost",
|
|
port=5432,
|
|
database="headquarter",
|
|
)
|
|
)
|
|
async with engine.begin() as connection:
|
|
await connection.run_sync(Base.metadata.create_all)
|
|
await connection.execute(text("TRUNCATE TABLE refresh_tokens, users RESTART IDENTITY CASCADE"))
|
|
await engine.dispose()
|
|
|
|
asyncio.run(_run())
|
|
|
|
|
|
def _load_app():
|
|
import importlib
|
|
import src.database as database_module
|
|
import src.api.users as users_module
|
|
import src.main as main_module
|
|
|
|
importlib.reload(database_module)
|
|
importlib.reload(users_module)
|
|
importlib.reload(main_module)
|
|
return main_module.app
|
|
|
|
|
|
def _insert_test_user(user_id: str) -> None:
|
|
async def _run() -> None:
|
|
engine = create_async_engine(
|
|
build_database_url(
|
|
user="headquarter",
|
|
password="headquarter",
|
|
host="localhost",
|
|
port=5432,
|
|
database="headquarter",
|
|
)
|
|
)
|
|
async with engine.begin() as connection:
|
|
await connection.run_sync(Base.metadata.create_all)
|
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
|
|
|
session_factory = async_sessionmaker(engine, expire_on_commit=False)
|
|
async with session_factory() as session:
|
|
user = User(
|
|
id=uuid.UUID(user_id),
|
|
email="test@headquarter.local",
|
|
name="Test User",
|
|
authentik_id="test-user",
|
|
avatar_url=None,
|
|
)
|
|
await session.merge(user)
|
|
await session.commit()
|
|
await engine.dispose()
|
|
|
|
asyncio.run(_run())
|
|
|
|
|
|
def _create_auth_cookie(user_id: str) -> str:
|
|
settings = Settings()
|
|
return mint_access_token(
|
|
settings=settings,
|
|
subject=user_id,
|
|
email="test@headquarter.local",
|
|
name="Test User",
|
|
expires_at=datetime.now(UTC) + timedelta(minutes=15),
|
|
)
|
|
|
|
|
|
@pytest.mark.integration
|
|
def test_get_profile_returns_401_without_cookie() -> None:
|
|
_prepare_users_test_db()
|
|
app = _load_app()
|
|
|
|
client = TestClient(app)
|
|
response = client.get("/users/me")
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
@pytest.mark.integration
|
|
def test_get_profile_returns_user_data() -> None:
|
|
_prepare_users_test_db()
|
|
user_id = "7f4b7ad8-c4ce-4d1b-8c83-7ce0f4f66dfb"
|
|
_insert_test_user(user_id)
|
|
app = _load_app()
|
|
|
|
client = TestClient(app)
|
|
client.cookies.set("access_token", _create_auth_cookie(user_id))
|
|
response = client.get("/users/me")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["email"] == "test@headquarter.local"
|
|
assert data["name"] == "Test User"
|
|
assert data["avatar_url"] is None
|
|
|
|
|
|
@pytest.mark.integration
|
|
def test_update_profile_changes_name_and_email() -> None:
|
|
_prepare_users_test_db()
|
|
user_id = "7f4b7ad8-c4ce-4d1b-8c83-7ce0f4f66dfb"
|
|
_insert_test_user(user_id)
|
|
app = _load_app()
|
|
|
|
client = TestClient(app)
|
|
client.cookies.set("access_token", _create_auth_cookie(user_id))
|
|
response = client.put("/users/me", json={"name": "Updated Name", "email": "updated@headquarter.local"})
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["name"] == "Updated Name"
|
|
assert data["email"] == "updated@headquarter.local"
|
|
|
|
|
|
@pytest.mark.integration
|
|
def test_update_profile_rejects_empty_name() -> None:
|
|
_prepare_users_test_db()
|
|
user_id = "7f4b7ad8-c4ce-4d1b-8c83-7ce0f4f66dfb"
|
|
_insert_test_user(user_id)
|
|
app = _load_app()
|
|
|
|
client = TestClient(app)
|
|
client.cookies.set("access_token", _create_auth_cookie(user_id))
|
|
response = client.put("/users/me", json={"name": " "})
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
@pytest.mark.integration
|
|
def test_update_profile_rejects_invalid_email() -> None:
|
|
_prepare_users_test_db()
|
|
user_id = "7f4b7ad8-c4ce-4d1b-8c83-7ce0f4f66dfb"
|
|
_insert_test_user(user_id)
|
|
app = _load_app()
|
|
|
|
client = TestClient(app)
|
|
client.cookies.set("access_token", _create_auth_cookie(user_id))
|
|
response = client.put("/users/me", json={"email": "not-an-email"})
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
@pytest.mark.integration
|
|
def test_upload_avatar_updates_avatar_url() -> None:
|
|
_prepare_users_test_db()
|
|
user_id = "7f4b7ad8-c4ce-4d1b-8c83-7ce0f4f66dfb"
|
|
_insert_test_user(user_id)
|
|
app = _load_app()
|
|
|
|
client = TestClient(app)
|
|
client.cookies.set("access_token", _create_auth_cookie(user_id))
|
|
|
|
# Create a simple 1x1 PNG
|
|
png_data = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100
|
|
response = client.post(
|
|
"/users/me/avatar",
|
|
files={"file": ("test.png", io.BytesIO(png_data), "image/png")},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["avatar_url"] is not None
|
|
assert data["avatar_url"].startswith("/uploads/avatars/")
|
|
|
|
|
|
@pytest.mark.integration
|
|
def test_upload_avatar_rejects_invalid_file_type() -> None:
|
|
_prepare_users_test_db()
|
|
user_id = "7f4b7ad8-c4ce-4d1b-8c83-7ce0f4f66dfb"
|
|
_insert_test_user(user_id)
|
|
app = _load_app()
|
|
|
|
client = TestClient(app)
|
|
client.cookies.set("access_token", _create_auth_cookie(user_id))
|
|
|
|
response = client.post(
|
|
"/users/me/avatar",
|
|
files={"file": ("test.txt", io.BytesIO(b"not an image"), "text/plain")},
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
@pytest.mark.integration
|
|
def test_upload_avatar_rejects_oversized_file() -> None:
|
|
_prepare_users_test_db()
|
|
user_id = "7f4b7ad8-c4ce-4d1b-8c83-7ce0f4f66dfb"
|
|
_insert_test_user(user_id)
|
|
app = _load_app()
|
|
|
|
client = TestClient(app)
|
|
client.cookies.set("access_token", _create_auth_cookie(user_id))
|
|
|
|
large_png = b"\x89PNG\r\n\x1a\n" + b"\x00" * (3 * 1024 * 1024) # 3MB
|
|
response = client.post(
|
|
"/users/me/avatar",
|
|
files={"file": ("large.png", io.BytesIO(large_png), "image/png")},
|
|
)
|
|
|
|
assert response.status_code == 400
|