"""card 도메인 스코프 e2e — 개인 카드는 소유자만, 전체(공용) 카드는 누구나. 스코프 규칙: user_id 있으면 개인(본인만 조회·관리) / NULL 이면 전체(모든 유저 조회·수정·삭제). 로그인은 auth_headers.""" async def _create_card(client, headers, *, number, name="카드", is_shared=False, is_wildcard=False): r = await client.post( "/v1/card/create", json={ "is_wildcard": is_wildcard, "is_shared": is_shared, "name": name, "number": number, "script": "안녕하세요", }, headers=headers, ) body = r.json() assert body["result"]["success"] is True, body return body["card"]["nego_card_id"] async def _list_numbers(client, headers): r = await client.get("/v1/card/list", headers=headers) return {c["number"] for c in r.json().get("cards", [])} async def test_personal_card_is_owner_only(client, auth_headers): """검증: user_id 가 박힌 개인 카드는 소유자 목록·단건조회에만 노출되고 타 유저에겐 숨는다. 기대결과: A 목록엔 있고 B 목록엔 없음, B 의 단건 조회는 success=False(CARD_NOT_FOUND).""" ha = await auth_headers("cardA") hb = await auth_headers("cardB") cid = await _create_card(client, ha, number="P-1", is_shared=False) assert "P-1" in await _list_numbers(client, ha) assert "P-1" not in await _list_numbers(client, hb) assert (await client.get(f"/v1/card/{cid}", headers=hb)).json()["result"]["success"] is False async def test_shared_card_visible_to_all(client, auth_headers): """검증: is_shared=True 카드는 user_id NULL 로 저장돼 모든 유저 목록·단건조회에 노출된다. 기대결과: A·B 목록 모두에 존재, 비생성자 B 의 단건 조회 success=True, is_shared 플래그 True.""" ha = await auth_headers("cardSA") hb = await auth_headers("cardSB") cid = await _create_card(client, ha, number="S-1", is_shared=True) assert "S-1" in await _list_numbers(client, ha) assert "S-1" in await _list_numbers(client, hb) got = await client.get(f"/v1/card/{cid}", headers=hb) assert got.json()["result"]["success"] is True assert got.json()["card"]["is_shared"] is True async def test_shared_card_editable_and_deletable_by_anyone(client, auth_headers): """검증: 전체(공용) 카드는 소유자가 없어 아무 유저나 수정·삭제 가능(정책: 누구나). 기대결과: 비생성자 B 의 수정·삭제 모두 success=True, 삭제 후 목록에서 사라짐.""" ha = await auth_headers("cardEA") hb = await auth_headers("cardEB") cid = await _create_card(client, ha, number="S-EDIT", is_shared=True) upd = await client.patch(f"/v1/card/update/{cid}", json={"name": "B가 수정"}, headers=hb) assert upd.json()["result"]["success"] is True dele = await client.delete(f"/v1/card/delete/{cid}", headers=hb) assert dele.json()["result"]["success"] is True assert "S-EDIT" not in await _list_numbers(client, hb)