Merge remote-tracking branch 'origin/sub_laptop_6' into sub_laptop_8

This commit is contained in:
2026-10-25 20:03:53 +09:00
4 changed files with 384 additions and 1 deletions
+149
View File
@@ -0,0 +1,149 @@
"""D02 기준 지정(PLAN 529-3) — 경위도 ↔ TM 변환 · 지점 정본 API(잇기 · 빈 연도 · 권한).
./venv/Scripts/python.exe -m pytest resources/tester/test_529_3_plan_points.py -q
"""
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from common_util.common_util_auth import require_company
from common_util.common_util_crs import lonlat_to_tm, tm_epsg_for_lon, tm_to_lonlat
from D02_PlanBase import D02_PlanBase_Router as router_mod
PID = "12953f5a-5ba1-42ad-a427-8ac857a58bc2"
URL = f"/api/projects/{PID}/plan/points"
# --- 좌표 변환 ---
@pytest.mark.parametrize("epsg, lon0", [(5185, 125.0), (5186, 127.0), (5187, 129.0)])
def test_origin_is_false_origin(epsg, lon0) -> None:
x, y = lonlat_to_tm(lon0, 38.0, epsg) # 원점 = (200 000, 600 000)
assert abs(x - 200000.0) < 0.001 and abs(y - 600000.0) < 0.001
@pytest.mark.parametrize("lon, lat", [(127.3851, 36.3504), (125.7, 34.1), (129.36, 36.02)])
def test_round_trip_within_1mm(lon, lat) -> None:
epsg = tm_epsg_for_lon(lon)
x, y = lonlat_to_tm(lon, lat, epsg)
back = lonlat_to_tm(*tm_to_lonlat(x, y, epsg), epsg)
assert abs(back[0] - x) < 0.001 and abs(back[1] - y) < 0.001
def test_belt_by_longitude() -> None:
assert [tm_epsg_for_lon(v) for v in (124.5, 125.999, 126.0, 127.999, 128.0, 131.8)] == [
5185,
5185,
5186,
5186,
5187,
5187,
]
# --- 지점 정본 API ---
A, B, C = (
{"lon": 127.10, "lat": 37.50},
{"lon": 127.12, "lat": 37.51},
{"lon": 127.15, "lat": 37.52},
)
@pytest.fixture
def api(monkeypatch):
state = {
"project": {"project_kind": "PLAN", "project_year": 2026, "project_year_end": 2027},
"rows": {},
"session": {"role": "USER", "email": "kim@forest.go.kr", "company_id": 1},
"stores": [],
}
async def load(project_id):
return state["project"], {y: dict(r) for y, r in state["rows"].items()}
async def store(project_id, epsg, writes, drops):
state["stores"].append((epsg, writes, drops))
for row in writes:
state["rows"][row["year"]] = {**row, "epsg": epsg}
for year in drops:
state["rows"].pop(year)
monkeypatch.setattr(router_mod, "_load", load)
monkeypatch.setattr(router_mod, "_store", store)
app = FastAPI()
app.include_router(router_mod.router)
app.dependency_overrides[require_company] = lambda: state["session"]
client = TestClient(app)
client.state = state
return client
def test_put_chains_years(api) -> None:
# 2027 시점을 엉뚱하게 보내도 서버가 2026 종점으로 맞춤
body = {"years": [{"year": 2026, "start": A, "end": B}, {"year": 2027, "start": A, "end": C}]}
got = api.put(URL, json=body)
assert got.status_code == 200
data = got.json()
assert data["epsg"] == 5186 and data["changed_years"] == [2026, 2027]
y26, y27 = data["years"]
assert y27["start"]["x"] == y26["end"]["x"] and y27["start"]["y"] == y26["end"]["y"]
assert abs(y27["start"]["lon"] - B["lon"]) < 1e-8 and abs(y27["start"]["lat"] - B["lat"]) < 1e-8
assert (y26["start"]["x"], y26["start"]["y"]) == pytest.approx(
lonlat_to_tm(127.10, 37.50, 5186)
)
def test_put_rejects_empty_and_out_of_range(api) -> None:
only_first = {"years": [{"year": 2026, "start": A, "end": B}]}
got = api.put(URL, json=only_first)
assert got.status_code == 400 and "2027년 종점" in got.json()["detail"]
no_start = {"years": [{"year": 2026, "end": B}, {"year": 2027, "end": C}]}
assert "2026년 시점" in api.put(URL, json=no_start).json()["detail"]
outside = {"years": [{"year": 2026, "start": A, "end": B}, {"year": 2028, "end": C}]}
assert api.put(URL, json=outside).status_code == 400
assert api.state["stores"] == []
def test_put_forest_only(api) -> None:
body = {"years": [{"year": 2026, "start": A, "end": B}, {"year": 2027, "end": C}]}
api.state["session"] = {"role": "USER", "email": "lee@company.com", "company_id": 1}
assert api.put(URL, json=body).status_code == 403
api.state["session"] = {"role": "SYSTEM_ADMIN", "email": "admin@x.com", "company_id": None}
assert api.put(URL, json=body).status_code == 200
def test_put_clears_only_changed_years(api) -> None:
body = {"years": [{"year": 2026, "start": A, "end": B}, {"year": 2027, "end": C}]}
api.put(URL, json=body)
# 같은 지점 다시 저장 = 쓰기 없음(분석 노선 그대로) · 2027 종점만 옮기면 2027 만
assert api.put(URL, json=body).json()["changed_years"] == []
moved = {"years": [{"year": 2026, "start": A, "end": B}, {"year": 2027, "end": A}]}
assert api.put(URL, json=moved).json()["changed_years"] == [2027]
assert len(api.state["stores"]) == 2
def test_get_lists_every_year_and_drops_outside(api) -> None:
api.state["rows"][2030] = {
"year": 2030,
"start_x": 1,
"start_y": 1,
"end_x": 2,
"end_y": 2,
"epsg": 5186,
}
data = api.get(URL).json()
assert [y["year"] for y in data["years"]] == [2026, 2027]
assert data["years"][0]["start"] is None
body = {"years": [{"year": 2026, "start": A, "end": B}, {"year": 2027, "end": C}]}
api.put(URL, json=body)
assert api.state["stores"][-1][2] == [2030] and 2030 not in api.state["rows"]
def test_not_plan_project(api) -> None:
api.state["project"] = {"project_kind": "ROAD", "project_year": 2026, "project_year_end": None}
# _load 대역이 그대로 돌려주므로 진짜 _load 의 거절은 서버 시험(ORCA)에서
assert api.get(URL).status_code == 200