# backend/tests/test_multiuser_isolation.py — per-user 격리(IDOR 404 + 도메인 스코프). 가장 중요. import pytest def _login(c, email, pw="demo-1234"): return c.post("/api/auth/login", json={"email": email, "password": pw}) def test_user_b_cannot_see_user_a_task(client_auth): _login(client_auth, "jiwoo@lumi.co") a_task = client_auth.post( "/api/tasks", json={"title": "A의 비밀 작업", "project_id": "me"} ).json() client_auth.post("/api/auth/logout") _login(client_auth, "hyunwoo@lumi.co") # 직접 id 조회 → 404(존재 숨김) assert client_auth.get(f"/api/tasks/{a_task['id']}").status_code == 404 # B 의 목록(스코프=현우, 데이터 없음)에 A 작업 없음 flat = client_auth.get("/api/tasks").json() assert all(t["id"] != a_task["id"] for t in flat) def test_create_owner_is_current_user_not_body(client_auth): # 바디에 user_id 를 넣어도 무시(질량 할당/IDOR 방어) — current_user 가 소유자 _login(client_auth, "hyunwoo@lumi.co") t = client_auth.post( "/api/tasks", json={"title": "현우 작업", "project_id": "me", "user_id": "jiwoo"} ).json() # 현우 목록엔 보이고 assert any(x["id"] == t["id"] for x in client_auth.get("/api/tasks").json()) # 지우 목록엔 없음 client_auth.post("/api/auth/logout") _login(client_auth, "jiwoo@lumi.co") assert all(x["id"] != t["id"] for x in client_auth.get("/api/tasks").json()) def test_b_starts_empty_a_full(client_auth): # phase-16+: 작업 시드 제거 → A(지우)가 작업을 만들면 A 만 보이고 B(현우)는 빈 상태 _login(client_auth, "jiwoo@lumi.co") client_auth.post("/api/tasks", json={"title": "지우 작업", "project_id": "me"}) assert len(client_auth.get("/api/tasks").json()) > 0 # 지우는 자기 작업 보임 client_auth.post("/api/auth/logout") _login(client_auth, "hyunwoo@lumi.co") assert client_auth.get("/api/tasks").json() == [] assert client_auth.get("/api/inbox").json() == [] assert client_auth.get("/api/mail").json() == [] notif = client_auth.get("/api/notifications").json() assert notif["now"] == [] and notif["later"] == [] and notif["held"] == [] @pytest.mark.parametrize( "path", ["/api/tasks", "/api/inbox", "/api/mail", "/api/notifications"] ) def test_b_lists_scoped_no_a_leak(client_auth, path): _login(client_auth, "hyunwoo@lumi.co") r = client_auth.get(path) assert r.status_code == 200 # 빈 스코프여도 200(빈 응답)