커머스 검색요청 계약을 프레임워크(protocol+service+RemoveNoneResponse)로 재구성.
실제 검색은 워커가 큐에서 꺼내 수행하도록 API 는 적재까지만 담당(비동기 분리).
- POST /v1/lps/search: 상품 리스트 → 상품별 SEARCH 잡 적재, product_code 로 활성 중복 방지, job_type→우선순위 매핑
- GET /v1/lps/jobs/{job_id}: 잡 상태/시도/결과 조회
- GET /v1/lps/queue/stats: 상태별 카운트(모니터링)
- protocol/lps_service 추가, job_crud.get() 단건조회, enums LPS_JOB_NOT_FOUND
- tests: 적재/중복/상태/미존재/통계 5건 (ASGI 클라이언트 + 실 lps_db) → 전체 16/16
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
62 lines
2.5 KiB
Python
62 lines
2.5 KiB
Python
"""LPS API 라우터 테스트 — 검색 적재/중복/상태조회/큐 통계 (ASGI 클라이언트 + 실 lps_db)."""
|
|
|
|
import pytest_asyncio
|
|
from sqlalchemy import text
|
|
|
|
|
|
@pytest_asyncio.fixture
|
|
async def clean_jobs(db_engine):
|
|
async with db_engine.begin() as conn:
|
|
await conn.execute(text("TRUNCATE job"))
|
|
|
|
|
|
async def test_search_enqueues_jobs(client, clean_jobs):
|
|
body = {"data": [
|
|
{"product_code": "P1", "product_name": "커피", "job_type": "new"},
|
|
{"product_code": "P2", "product_name": "무선마우스", "specification": "M170"},
|
|
]}
|
|
r = await client.post("/v1/lps/search", json=body)
|
|
assert r.status_code == 200
|
|
j = r.json()
|
|
assert j["accepted"] == 2
|
|
assert {i["product_code"] for i in j["items"]} == {"P1", "P2"}
|
|
assert all(i.get("job_id") for i in j["items"])
|
|
|
|
|
|
async def test_search_dedupes_active_product(client, clean_jobs):
|
|
body = {"data": [{"product_code": "P1", "product_name": "커피", "job_type": "new"}]}
|
|
await client.post("/v1/lps/search", json=body)
|
|
r2 = await client.post("/v1/lps/search", json=body) # 같은 product_code 재요청
|
|
item = r2.json()["items"][0]
|
|
assert item["duplicated"] is True
|
|
assert "job_id" not in item # None → RemoveNoneResponse 로 제거됨
|
|
assert r2.json()["accepted"] == 0
|
|
|
|
|
|
async def test_job_status_flow(client, clean_jobs):
|
|
jid = (await client.post("/v1/lps/search", json={"data": [{"product_code": "P9", "product_name": "커피"}]})).json()["items"][0]["job_id"]
|
|
r = await client.get(f"/v1/lps/jobs/{jid}")
|
|
body = r.json()
|
|
assert body["status"] == "PENDING" and body["attempts"] == 0
|
|
assert body["result"]["success"] is True
|
|
|
|
|
|
async def test_job_status_not_found(client, clean_jobs):
|
|
# 존재하지 않는(유효 UUID) 잡
|
|
r = await client.get("/v1/lps/jobs/00000000-0000-0000-0000-000000000000")
|
|
assert r.json()["result"]["success"] is False
|
|
assert r.json()["result"]["desc"] == "LPS_JOB_NOT_FOUND"
|
|
# 잘못된 형식의 id 도 not-found 처리
|
|
r2 = await client.get("/v1/lps/jobs/not-a-uuid")
|
|
assert r2.json()["result"]["desc"] == "LPS_JOB_NOT_FOUND"
|
|
|
|
|
|
async def test_queue_stats(client, clean_jobs):
|
|
await client.post("/v1/lps/search", json={"data": [
|
|
{"product_code": "A", "product_name": "x"},
|
|
{"product_code": "B", "product_name": "y"},
|
|
]})
|
|
r = await client.get("/v1/lps/queue/stats")
|
|
counts = r.json()["counts"]
|
|
assert counts["PENDING"] == 2 and counts["DONE"] == 0 and counts["DEAD"] == 0
|