Add ruff linter/formatter for Python
- Add ruff as dev dependency - Configure ruff in pyproject.toml with strict 88-char line limit - Ignore B008 (FastAPI Depends pattern is standard) - Allow longer lines in tests for readability - Fix all lint issues in source files - Add Makefile targets: lint-backend, format-backend, fix-backend
This commit is contained in:
parent
69bc8413e0
commit
6c218130e9
31 changed files with 1234 additions and 876 deletions
|
|
@ -7,28 +7,28 @@ os.environ.setdefault("SECRET_KEY", "test-secret-key-for-testing-only")
|
|||
import pytest
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
|
||||
from auth import get_password_hash
|
||||
from database import Base, get_db
|
||||
from main import app
|
||||
from models import User, Role, Permission, ROLE_DEFINITIONS, ROLE_REGULAR, ROLE_ADMIN
|
||||
from auth import get_password_hash
|
||||
from models import ROLE_ADMIN, ROLE_DEFINITIONS, ROLE_REGULAR, Role, User
|
||||
from tests.helpers import unique_email
|
||||
|
||||
TEST_DATABASE_URL = os.getenv(
|
||||
"TEST_DATABASE_URL",
|
||||
"postgresql+asyncpg://postgres:postgres@localhost:5432/arbret_test"
|
||||
"postgresql+asyncpg://postgres:postgres@localhost:5432/arbret_test",
|
||||
)
|
||||
|
||||
|
||||
class ClientFactory:
|
||||
"""Factory for creating httpx clients with optional cookies."""
|
||||
|
||||
|
||||
def __init__(self, transport, base_url, session_factory):
|
||||
self._transport = transport
|
||||
self._base_url = base_url
|
||||
self._session_factory = session_factory
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def create(self, cookies: dict | None = None):
|
||||
"""Create a new client, optionally with cookies set."""
|
||||
|
|
@ -38,15 +38,15 @@ class ClientFactory:
|
|||
cookies=cookies or {},
|
||||
) as client:
|
||||
yield client
|
||||
|
||||
|
||||
async def request(self, method: str, url: str, **kwargs):
|
||||
"""Make a one-off request without cookies."""
|
||||
async with self.create() as client:
|
||||
return await client.request(method, url, **kwargs)
|
||||
|
||||
|
||||
async def get(self, url: str, **kwargs):
|
||||
return await self.request("GET", url, **kwargs)
|
||||
|
||||
|
||||
async def post(self, url: str, **kwargs):
|
||||
return await self.request("POST", url, **kwargs)
|
||||
|
||||
|
|
@ -64,16 +64,16 @@ async def setup_roles(db: AsyncSession) -> dict[str, Role]:
|
|||
# Check if role exists
|
||||
result = await db.execute(select(Role).where(Role.name == role_name))
|
||||
role = result.scalar_one_or_none()
|
||||
|
||||
|
||||
if not role:
|
||||
role = Role(name=role_name, description=config["description"])
|
||||
db.add(role)
|
||||
await db.flush()
|
||||
|
||||
|
||||
# Set permissions
|
||||
await role.set_permissions(db, config["permissions"])
|
||||
roles[role_name] = role
|
||||
|
||||
|
||||
await db.commit()
|
||||
return roles
|
||||
|
||||
|
|
@ -91,9 +91,11 @@ async def create_user_with_roles(
|
|||
result = await db.execute(select(Role).where(Role.name == role_name))
|
||||
role = result.scalar_one_or_none()
|
||||
if not role:
|
||||
raise ValueError(f"Role '{role_name}' not found. Did you run setup_roles()?")
|
||||
raise ValueError(
|
||||
f"Role '{role_name}' not found. Did you run setup_roles()?"
|
||||
)
|
||||
roles.append(role)
|
||||
|
||||
|
||||
user = User(
|
||||
email=email,
|
||||
hashed_password=get_password_hash(password),
|
||||
|
|
@ -110,27 +112,27 @@ async def client_factory():
|
|||
"""Fixture that provides a factory for creating clients."""
|
||||
engine = create_async_engine(TEST_DATABASE_URL)
|
||||
session_factory = async_sessionmaker(engine, expire_on_commit=False)
|
||||
|
||||
|
||||
# Create tables
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
|
||||
# Setup roles
|
||||
async with session_factory() as db:
|
||||
await setup_roles(db)
|
||||
|
||||
|
||||
async def override_get_db():
|
||||
async with session_factory() as session:
|
||||
yield session
|
||||
|
||||
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
|
||||
|
||||
transport = ASGITransport(app=app)
|
||||
factory = ClientFactory(transport, "http://test", session_factory)
|
||||
|
||||
|
||||
yield factory
|
||||
|
||||
|
||||
app.dependency_overrides.clear()
|
||||
await engine.dispose()
|
||||
|
||||
|
|
@ -147,17 +149,17 @@ async def regular_user(client_factory):
|
|||
"""Create a regular user and return their credentials and cookies."""
|
||||
email = unique_email("regular")
|
||||
password = "password123"
|
||||
|
||||
|
||||
async with client_factory.get_db_session() as db:
|
||||
user = await create_user_with_roles(db, email, password, [ROLE_REGULAR])
|
||||
user_id = user.id
|
||||
|
||||
|
||||
# Login to get cookies
|
||||
response = await client_factory.post(
|
||||
"/api/auth/login",
|
||||
json={"email": email, "password": password},
|
||||
)
|
||||
|
||||
|
||||
return {
|
||||
"email": email,
|
||||
"password": password,
|
||||
|
|
@ -172,17 +174,17 @@ async def alt_regular_user(client_factory):
|
|||
"""Create a second regular user for tests needing multiple users."""
|
||||
email = unique_email("alt_regular")
|
||||
password = "password123"
|
||||
|
||||
|
||||
async with client_factory.get_db_session() as db:
|
||||
user = await create_user_with_roles(db, email, password, [ROLE_REGULAR])
|
||||
user_id = user.id
|
||||
|
||||
|
||||
# Login to get cookies
|
||||
response = await client_factory.post(
|
||||
"/api/auth/login",
|
||||
json={"email": email, "password": password},
|
||||
)
|
||||
|
||||
|
||||
return {
|
||||
"email": email,
|
||||
"password": password,
|
||||
|
|
@ -197,16 +199,16 @@ async def admin_user(client_factory):
|
|||
"""Create an admin user and return their credentials and cookies."""
|
||||
email = unique_email("admin")
|
||||
password = "password123"
|
||||
|
||||
|
||||
async with client_factory.get_db_session() as db:
|
||||
await create_user_with_roles(db, email, password, [ROLE_ADMIN])
|
||||
|
||||
|
||||
# Login to get cookies
|
||||
response = await client_factory.post(
|
||||
"/api/auth/login",
|
||||
json={"email": email, "password": password},
|
||||
)
|
||||
|
||||
|
||||
return {
|
||||
"email": email,
|
||||
"password": password,
|
||||
|
|
@ -220,16 +222,16 @@ async def user_no_roles(client_factory):
|
|||
"""Create a user with NO roles and return their credentials and cookies."""
|
||||
email = unique_email("noroles")
|
||||
password = "password123"
|
||||
|
||||
|
||||
async with client_factory.get_db_session() as db:
|
||||
await create_user_with_roles(db, email, password, [])
|
||||
|
||||
|
||||
# Login to get cookies
|
||||
response = await client_factory.post(
|
||||
"/api/auth/login",
|
||||
json={"email": email, "password": password},
|
||||
)
|
||||
|
||||
|
||||
return {
|
||||
"email": email,
|
||||
"password": password,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue