feat(worker-pool): Registry-1B Pull 활성화 (auth + worker_jobs + 5 endpoint)
worker-pool-policy §B 1B 영역 완료. 1A scaffold (mig 270~274 + 503 stub) 위에:
- mig 275/276: worker_jobs (status CHECK + user_id=owner) + pending partial index
- create_laptop_worker_bot_token + require_worker_user dependency (voice-memo 동형)
- /internal/worker/{register,heartbeat,claim,result,drain} 5 endpoint 실 구현
- /claim FOR UPDATE SKIP LOCKED + 204 body 0
- /result 소유권 검증 (worker_id 매칭, 404) + failed 재시도 (attempts/max)
- explicit failure 시 request.result 무시 (DB result NULL 유지)
- 테스트 22 항목 7 파일
policy §B.2 5 invariant 보존: voice-memo wrapper 변경 0, drain advisory,
result raw JSONB, ProcessingQueue 무변경, 운영 자동 분기 변경 0.
활용처 (recap context + /jobs/recap + payload 100KB guard) = Registry-1C 영역.
stale recovery / 노트북 client / canonical promote = Notebook-Pilot-1 영역.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user