Files
hyungi_document_server/tests/test_worker_jobs_skip_locked.py
T
Hyungi Ahn 0cbd97fcba refactor(worker-pool): Registry-1B test fixture — NullPool helper standalone
각 helper 가 자체 engine + NullPool 사용 (connection 격리). fixture chain 의
asyncpg "another operation in progress" race 회피. 호출 site 단순화.

같은 파일 sequential 실행 시 module-level app + global engine pool 충돌은
별 follow-up `PR-Worker-Pool-Test-Fixture-Isolation` (P3) 영역.

단독 PASS 검증: auth 5/5 + smoke 3/3 + ownership 1/1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 12:43:53 +09:00

99 lines
3.0 KiB
Python

"""PR-Worker-Pool-Registry-1B — /claim 동시성 (SKIP LOCKED) + 204 body 검증.
2 항목:
1. 두 client 동시 claim → 한쪽 200, 다른 쪽 204 + body 0 (정정 #4)
2. queue empty 호출 → 204 + body 0 explicit
"""
from __future__ import annotations
import asyncio
import os
import sys
import uuid
import pytest
import pytest_asyncio
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "app"))
from httpx import ASGITransport, AsyncClient
from _worker_pool_helpers import (
cleanup_worker_capabilities,
cleanup_worker_jobs,
ensure_user,
insert_worker_capability,
insert_worker_job,
mint_access_token,
)
@pytest_asyncio.fixture
async def env_setup(monkeypatch):
monkeypatch.setenv("LAPTOP_WORKER_BOT_USERNAME", "laptop-worker-bot")
@pytest_asyncio.fixture
async def worker_token(env_setup):
await ensure_user("laptop-worker-bot")
return mint_access_token("laptop-worker-bot")
@pytest.mark.asyncio
async def test_skip_locked_only_one_winner(worker_token):
"""두 client 동시 동일 job_type claim → 한쪽 200, 다른 쪽 204."""
from main import app
owner_id = await ensure_user("test-owner-skip-1b")
jt = f"test-skip-1b-{uuid.uuid4().hex[:8]}"
w1 = f"test-skip-1b-w1-{uuid.uuid4().hex[:6]}"
w2 = f"test-skip-1b-w2-{uuid.uuid4().hex[:6]}"
for w in (w1, w2):
await insert_worker_capability(w, owner_id)
await insert_worker_job(owner_id, jt)
headers = {"Authorization": f"Bearer {worker_token}"}
try:
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as c1, AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as c2:
r1, r2 = await asyncio.gather(
c1.post(
"/internal/worker/claim",
json={"worker_id": w1, "job_type": jt},
headers=headers,
),
c2.post(
"/internal/worker/claim",
json={"worker_id": w2, "job_type": jt},
headers=headers,
),
)
codes = sorted([r1.status_code, r2.status_code])
assert codes == [200, 204], f"unexpected codes: {codes}"
loser = r1 if r1.status_code == 204 else r2
assert loser.content == b"" # 정정 #4
finally:
await cleanup_worker_jobs("test-skip-1b")
await cleanup_worker_capabilities("test-skip-1b-w")
@pytest.mark.asyncio
async def test_claim_204_body_explicit_empty(worker_token):
"""queue empty 호출 → 204 + body 0."""
from main import app
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as c:
r = await c.post(
"/internal/worker/claim",
json={"worker_id": "test-skip-1b-empty", "job_type": "test-skip-1b-NOEXIST"},
headers={"Authorization": f"Bearer {worker_token}"},
)
assert r.status_code == 204
assert r.content == b""