"""수집 시작 e2e — 비동기 잡 적재와 진입 게이트.""" from common.enums import ErrorType, JobStatus, JobType, LinkChannel, SourceType OTA = "https://www.yanolja.com/pension/1" async def _place(client, h, kakao=None, name="테스트펜션"): pid = (await client.post("/v1/place", headers=h, json={"name": name, "category": 1})).json()["place"]["place_id"] if kakao: await client.post(f"/v1/place/{pid}/verify", headers=h, json={"external_place_id": kakao}) return pid async def _confirmed_link(client, h, pid, url=OTA): lid = (await client.post(f"/v1/place/{pid}/link", headers=h, json={ "channel": LinkChannel.YANOLJA.value, "url": url, "discovered_by": SourceType.API.value, })).json()["link"]["link_id"] await client.post(f"/v1/place/{pid}/link/{lid}/confirm", headers=h) return lid async def test_collect_requires_verified_place(auth_headers, client): """검증: 동일 업소 검증 전에 수집을 시작한다.""" h = await auth_headers("u1") pid = await _place(client, h) r = await client.post(f"/v1/place/{pid}/collect", headers=h, json={}) assert r.json()["result"]["code"] == ErrorType.PLACE_NOT_VERIFIED.value async def test_collect_starts_without_any_link(auth_headers, client): """검증: 링크가 하나도 없는 검증된 사업장에서 수집을 시작한다.""" h = await auth_headers("u1") pid = await _place(client, h, kakao="c1") body = (await client.post(f"/v1/place/{pid}/collect", headers=h, json={})).json() assert body["result"]["success"] is True assert body["job_id"] assert body["confirmed_links"] == 0 async def test_targeted_recrawl_needs_confirmed_link(auth_headers, client): """검증: link_ids 로 특정 링크만 재크롤하라고 했는데 그게 확정 상태가 아니다.""" import uuid as _uuid h = await auth_headers("u1") pid = await _place(client, h, kakao="c2") lid = (await client.post(f"/v1/place/{pid}/link", headers=h, json={"channel": 1, "url": OTA})).json()["link"]["link_id"] r = await client.post(f"/v1/place/{pid}/collect", headers=h, json={"link_ids": [lid]}) assert r.json()["result"]["code"] == ErrorType.LINK_NOT_CONFIRMED.value async def test_collect_enqueues_job_and_returns_immediately(auth_headers, client): """검증: 검증 + 링크 확정이 끝난 뒤 수집을 시작한다.""" h = await auth_headers("u1") pid = await _place(client, h, kakao="c3") await _confirmed_link(client, h, pid) body = (await client.post(f"/v1/place/{pid}/collect", headers=h, json={})).json() assert body["result"]["success"] is True assert body["job_id"] assert body["status"] == JobStatus.PENDING.value assert body["created"] is True assert body["confirmed_links"] == 1 job = (await client.get(f"/v1/job/{body['job_id']}", headers=h)).json() assert job["job"]["job_type"] == JobType.COLLECT.value assert job["job"]["status"] == JobStatus.PENDING.value async def test_collect_marks_place_as_collecting(auth_headers, client): """검증: 수집을 시작한 뒤 사업장 상태.""" h = await auth_headers("u1") pid = await _place(client, h, kakao="c4") await _confirmed_link(client, h, pid) await client.post(f"/v1/place/{pid}/collect", headers=h, json={}) place = (await client.get(f"/v1/place/{pid}", headers=h)).json()["place"] assert place["status"] == 2 # PlaceStatus.COLLECTING async def test_double_click_does_not_run_twice(auth_headers, client): """검증: 수집 버튼을 두 번 누른다.""" h = await auth_headers("u1") pid = await _place(client, h, kakao="c5") await _confirmed_link(client, h, pid) first = (await client.post(f"/v1/place/{pid}/collect", headers=h, json={})).json() second = (await client.post(f"/v1/place/{pid}/collect", headers=h, json={})).json() assert second["result"]["success"] is True assert second["job_id"] == first["job_id"] assert second["created"] is False async def test_default_collect_payload_does_not_freeze_existing_links(auth_headers, client): """기본 수집은 잡 안에서 새로 발견·확정되는 링크도 대상으로 삼는다.""" h = await auth_headers("u1") pid = await _place(client, h, kakao="c6") confirmed = await _confirmed_link(client, h, pid, "https://www.yanolja.com/pension/ok") await client.post(f"/v1/place/{pid}/link", headers=h, json={"channel": 3, "url": "https://place.naver.com/nope"}) body = (await client.post(f"/v1/place/{pid}/collect", headers=h, json={})).json() assert body["confirmed_links"] == 1 from crud.job_crud import JobQueue job = await JobQueue().get(body["job_id"]) assert job["payload"]["link_ids"] == [] assert job["payload"]["place_id"] == pid assert job["payload"]["discover_channels"] is False async def test_collect_payload_carries_explicit_channel_discovery_option(auth_headers, client): """Perplexity URL 발견은 사용자가 선택한 수집 회차에만 잡 payload 로 전달한다.""" h = await auth_headers("u1") pid = await _place(client, h, kakao="c6-discovery") body = (await client.post( f"/v1/place/{pid}/collect", headers=h, json={"discover_channels": True} )).json() from crud.job_crud import JobQueue job = await JobQueue().get(body["job_id"]) assert job["payload"]["discover_channels"] is True async def test_targeted_collect_payload_carries_requested_confirmed_link(auth_headers, client): """사용자가 특정 링크를 고른 재수집만 그 링크로 제한한다.""" h = await auth_headers("u1") pid = await _place(client, h, kakao="c6-targeted") confirmed = await _confirmed_link(client, h, pid, "https://www.yanolja.com/pension/target") body = (await client.post( f"/v1/place/{pid}/collect", headers=h, json={"link_ids": [confirmed]} )).json() from crud.job_crud import JobQueue job = await JobQueue().get(body["job_id"]) assert job["payload"]["link_ids"] == [confirmed] async def test_collect_is_scoped_to_owner(auth_headers, client): """검증: 다른 사장님 계정으로 남의 사업장 수집을 시작한다.""" h1 = await auth_headers("o1") pid = await _place(client, h1, kakao="c7") await _confirmed_link(client, h1, pid) h2 = await auth_headers("o2") r = await client.post(f"/v1/place/{pid}/collect", headers=h2, json={}) assert r.json()["result"]["code"] == ErrorType.PLACE_NOT_FOUND.value