Files
Aislo/resources/tester/test_529_3_plan_points.py
T
eomsangdonandClaude Opus 5.5 54e08d0d65 feat(D 예정노선): 설계 내용 = 프로젝트 폴더 한 파일 D03_PlanAnalysis/plan_network.json — 저장 모듈 · 서버 길 옮김 · 업체 연도 = 파일 조각 (PLAN 590-1 · 요구 452 · 453)
- 저장 모듈 D03_PlanAnalysis_Store — 읽기 · 원자 쓰기 · 판 · 옛 DB 칸 읽을 때 옮김(지점 셋 꼴 · 연도 행 접기 · 옛 연도 노선) · plan_state(조각 · 연도 지문 · 확정 = 지문 같을 때만)
- D03 [저장] · [초기화] · D02 지점 = 파일만 + 연도 상태 줄(지문 · 확정) · 프로젝트 잠금 · 파일 쓰기 실패 = DB 되돌림 · 빈 연도 줄 지움
- D04 요약 · 업체 지정 · 확정 메일 · 내보내기 · 대시보드 [연결] · 받은 예정노선 · 산림청 홈 · 개요 연결 = 파일에서 · 업체 줄 연도 칸 안 읽음(⑤)
- 산림청 홈 「분석 전」 = 노선 안 그은 예정노선 프로젝트 수 · plan-source 지운 원본 거름
- 옛 연도 단위 지정 · 옛 연도 행 노선 길 걷음(DB 실측 0) · 연결 때 설계속도 씨앗 걷음(591-1 이음)
- SQL 023 = 업체 줄 year NULL 허용(옛 칸은 590-3 에서 지움)
- 시험: 새 지킴 590_1(옮김 · 원자 쓰기 · 연도 읽기 · 잠금) · 낡은 시험 고침 — 529_3 · 530_2 · 531_3 · 531_d04 · 547_2 · 563_2(DB 조각 · 연도 행 흉내 → 파일 흉내 · 옛 연도 지정 시험 · 설계속도 씨앗 시험 지움 = 그 길 걷음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 09:52:51 +09:00

171 lines
6.4 KiB
Python

"""D02 기준 지정(PLAN 529-3 · 560-2 · 579-1 · 585-1 · 590-1) — 경위도 ↔ TM 변환 · 지점 목록 API ·
지점 = 설계 내용 파일 `points` · 옛 DB 칸(시점 · 종점 꼴 · 한 점 꼴 · 연도 행 접기)은 열 때 옮김 ·
저장된 좌표계 그대로.
./venv/Scripts/python.exe -m pytest resources/tester/test_529_3_plan_points.py -q
"""
import json
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
from D03_PlanAnalysis import D03_PlanAnalysis_Store as store
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, tmp_path):
"""파일 = tmp_path 의 진짜 파일 · 없으면 옛 DB 칸(`base_json` · 연도 행)을 옮김."""
state = {
"base_json": None,
"rows": [],
"root": tmp_path,
"session": {"role": "USER", "email": "kim@forest.go.kr", "company_id": 1},
}
async def load(project_id):
plan = store.read_plan(tmp_path)
return tmp_path, plan or store.migrate(state["base_json"], None, state["rows"])
monkeypatch.setattr(store, "load_plan", load)
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_lists(api) -> None:
got = api.put(URL, json={"points": [A, B, C]})
assert got.status_code == 200
data = got.json()
assert data["epsg"] == 5186 and len(data["points"]) == 3
assert (data["points"][0]["x"], data["points"][0]["y"]) == pytest.approx(
lonlat_to_tm(127.10, 37.50, 5186)
)
end = data["points"][2]
assert abs(end["lon"] - C["lon"]) < 1e-8 and abs(end["lat"] - C["lat"]) < 1e-8
stored = json.loads(store.plan_path(api.state["root"]).read_text("utf-8"))
assert stored["version"] == 1 and stored["epsg"] == 5186 and stored["lines"] == []
assert set(stored["points"][1]) == {"x", "y"}
def test_put_partial_and_empty(api) -> None:
data = api.put(URL, json={"points": [A]}).json()
assert len(data["points"]) == 1 and data["epsg"] == 5186
data = api.put(URL, json={}).json()
assert data == {"status": "success", "epsg": None, "points": []}
assert api.put(URL, json={"points": [A] * 40}).status_code == 200
assert api.put(URL, json={"points": [A] * 41}).status_code == 422 # 상한 40(옛 20 + 20)
def test_put_keeps_stored_epsg(api) -> None:
api.put(URL, json={"points": [A, C]})
far = {"lon": 128.9, "lat": 37.5} # 평균이 128° 를 넘어도 저장된 5186 그대로
data = api.put(URL, json={"points": [A, far, far]}).json()
assert data["epsg"] == 5186
def test_get_joins_old_start_end_lists(api) -> None:
a, b, c = (lonlat_to_tm(p["lon"], p["lat"], 5186) for p in (A, B, C))
old = {"epsg": 5186, "starts": [{"x": a[0], "y": a[1]}, {"x": b[0], "y": b[1]}],
"ends": [{"x": c[0], "y": c[1]}]} # fmt: skip
api.state["base_json"] = json.dumps(old)
data = api.get(URL).json()
assert [p["x"] for p in data["points"]] == pytest.approx([a[0], b[0], c[0]]) # 시점 뒤 종점
def test_get_wraps_old_single_points(api) -> None:
a, c = (lonlat_to_tm(p["lon"], p["lat"], 5186) for p in (A, C))
old = {"epsg": 5186, "start": {"x": a[0], "y": a[1]}, "end": {"x": c[0], "y": c[1]}}
api.state["base_json"] = json.dumps(old)
data = api.get(URL).json()
assert [p["x"] for p in data["points"]] == pytest.approx([a[0], c[0]])
old["end"] = None
api.state["base_json"] = json.dumps(old)
assert [p["x"] for p in api.get(URL).json()["points"]] == pytest.approx([a[0]])
def test_put_forest_only(api) -> None:
api.state["session"] = {"role": "USER", "email": "lee@company.com", "company_id": 1}
assert api.put(URL, json={"points": [A, B]}).status_code == 403
api.state["session"] = {"role": "SYSTEM_ADMIN", "email": "admin@x.com", "company_id": None}
assert api.put(URL, json={"points": [A, B]}).status_code == 200
def test_get_folds_old_year_rows(api) -> None:
epsg = 5186
a, b, c = (lonlat_to_tm(p["lon"], p["lat"], epsg) for p in (A, B, C))
for year, s, e in ((2026, a, b), (2027, b, c)):
api.state["rows"].append(
{
"year": year,
**{"start_x": s[0], "start_y": s[1], "end_x": e[0], "end_y": e[1]},
"epsg": epsg,
}
)
data = api.get(URL).json()
assert data["epsg"] == epsg
assert [p["x"] for p in data["points"]] == pytest.approx([a[0], c[0]])
def test_get_empty_new_project(api) -> None:
assert api.get(URL).json() == {"status": "success", "epsg": None, "points": []}
def test_put_keeps_line_epsg(api) -> None:
"""선이 있으면 지점을 다 지워도 좌표계 그대로(파일 머리 한 곳 · 선 좌표가 그 좌표계)."""
api.put(URL, json={"points": [A]})
path = store.plan_path(api.state["root"])
plan = json.loads(path.read_text("utf-8"))
plan["lines"] = [{"id": "main", "vertices": [[0.0, 0.0], [1.0, 0.0]], "branch": None}]
path.write_text(json.dumps(plan), "utf-8")
assert api.put(URL, json={}).json()["points"] == []
assert json.loads(path.read_text("utf-8"))["epsg"] == 5186