"""PR-Worker-Pool-Registry-1B 통합 테스트용 헬퍼. 각 테스트 파일이 import 해서 자체 fixture 안에서 호출. conftest.py 갱신은 회피 (타 테스트 영향 가능성, [[feedback_residue_grep_live_vs_history]] 정신). 사용 예: from _worker_pool_helpers import ( get_database_url, mint_access_token, ensure_user, cleanup_worker_jobs, cleanup_worker_capabilities, ) """ from __future__ import annotations import os import secrets import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "app")) from datetime import datetime, timedelta, timezone from jose import jwt from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession def get_database_url() -> str: return os.getenv( "DATABASE_URL", "postgresql+asyncpg://pkm:pkm@postgres:5432/pkm", ) def mint_access_token(username: str, expires_minutes: int = 60) -> str: """test 용 JWT access token (`core.auth.create_access_token` 와 동일 페이로드).""" from core.config import settings now = datetime.now(timezone.utc) payload = { "sub": username, "exp": now + timedelta(minutes=expires_minutes), "iat": int(now.timestamp()), "type": "access", } return jwt.encode(payload, settings.jwt_secret, algorithm="HS256") async def ensure_user( session: AsyncSession, username: str, is_active: bool = True ) -> int: """users row 존재 보장 + id 반환. 비밀번호 = random bcrypt hash.""" from core.auth import hash_password result = await session.execute( text("SELECT id FROM users WHERE username = :u"), {"u": username} ) row = result.first() if row is not None: return int(row[0]) h = hash_password(secrets.token_urlsafe(32)) inserted = await session.execute( text( "INSERT INTO users (username, password_hash, is_active, password_changed_at) " "VALUES (:u, :h, :a, NOW()) RETURNING id" ), {"u": username, "h": h, "a": is_active}, ) new_id = int(inserted.scalar_one()) await session.commit() return new_id async def cleanup_worker_jobs(session: AsyncSession, job_type_prefix: str) -> None: await session.execute( text("DELETE FROM worker_jobs WHERE job_type LIKE :p"), {"p": f"{job_type_prefix}%"}, ) await session.commit() async def cleanup_worker_capabilities(session: AsyncSession, worker_id_prefix: str) -> None: await session.execute( text("DELETE FROM worker_heartbeats WHERE worker_id LIKE :p"), {"p": f"{worker_id_prefix}%"}, ) await session.execute( text("DELETE FROM worker_capabilities WHERE worker_id LIKE :p"), {"p": f"{worker_id_prefix}%"}, ) await session.commit()