⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt` 가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는 시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음. - `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침** - `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시) - 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q` 옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0 ⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
74 lines
2.5 KiB
Python
74 lines
2.5 KiB
Python
"""URL의 project_id가 남의 회사 것이면 막히는지 (2026-09-01 신설 가드).
|
|
|
|
지금까지 B03~B07은 로그인·회사 소속만 보고 프로젝트 주인은 보지 않았다. 특히 B07 도각
|
|
저장은 그 회사의 공용 양식을 덮어쓰므로 영향이 크다. DB는 읽기만 한다.
|
|
"""
|
|
|
|
import asyncio
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
from common_util.common_util_auth import require_project_access # noqa: E402
|
|
from config.config_db import close_db_pool, get_db_pool, init_db_pool # noqa: E402
|
|
|
|
|
|
class _Request:
|
|
"""path_params만 있으면 되는 최소 요청 대역."""
|
|
|
|
def __init__(self, **path_params):
|
|
self.path_params = path_params
|
|
|
|
|
|
async def _checks():
|
|
await init_db_pool()
|
|
try:
|
|
pool = get_db_pool()
|
|
async with pool.acquire() as connection, connection.cursor() as cursor:
|
|
await cursor.execute(
|
|
"SELECT id, company_id FROM projects WHERE deleted_at IS NULL LIMIT 1"
|
|
)
|
|
row = await cursor.fetchone()
|
|
if not row:
|
|
return None
|
|
await _assert_guard(row[0], row[1])
|
|
return True
|
|
finally:
|
|
await close_db_pool()
|
|
|
|
|
|
def test_project_access_guard():
|
|
if asyncio.run(_checks()) is None:
|
|
pytest.skip("검사할 프로젝트가 DB에 없다")
|
|
|
|
|
|
async def _assert_guard(project_id, company_id):
|
|
|
|
owner = {"role": "USER", "company_id": company_id}
|
|
other = {"role": "USER", "company_id": company_id + 1000}
|
|
admin = {"role": "SYSTEM_ADMIN", "company_id": None}
|
|
|
|
# 같은 회사면 통과한다.
|
|
assert await require_project_access(_Request(project_id=project_id), owner) is owner
|
|
|
|
# 다른 회사면 403.
|
|
with pytest.raises(HTTPException) as blocked:
|
|
await require_project_access(_Request(project_id=project_id), other)
|
|
assert blocked.value.status_code == 403
|
|
|
|
# 없는 프로젝트는 404.
|
|
with pytest.raises(HTTPException) as missing:
|
|
await require_project_access(
|
|
_Request(project_id="00000000-0000-0000-0000-000000000000"), owner
|
|
)
|
|
assert missing.value.status_code == 404
|
|
|
|
# 시스템 관리자와 프로젝트 경로가 없는 요청은 그대로 지나간다.
|
|
assert await require_project_access(_Request(project_id=project_id), admin) is admin
|
|
assert await require_project_access(_Request(), other) is other
|