- 433-17① 이관 원천 = 저장 종단 파일 비정규 측점(서버가 직접 읽음) · 화면은 노선 id 만 보냄 — 초안 관 자리가 「기타」로 안 섞임 - 433-10 구조물 측점 다시 뜸(관 PUT · 측점 만들기) 끝에 옛 자리 cross_*.json 정리 - 433-18 서버 station_keys = 맨 앞 · 맨 끝 end:BP/EP 먼저 · 구간형 :start · :end · B06 stationKeysOf 도 맨 앞 · 맨 끝 end — 거울 시험 test_452 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
292 lines
11 KiB
Python
292 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""452 서버 — 구 구조물 이관 · 옛 횡단 파일 · 시종점 열쇠 (433-17① · 433-10 · 433-18).
|
|
|
|
못박는 것
|
|
- 이관 원천 = 저장 종단 파일의 비정규 측점 — 화면 본문(초안 관 자리 섞임)은 안 믿음 ·
|
|
저장 관 자리면 structures.json · pipe_points.json 그대로.
|
|
- 관 PUT 끝 측점 다시 뜸(`_regenerate_structure_stations`) = 옛 자리 cross_*.json 도 지움.
|
|
- 시종점 열쇠 한 규칙 — 맨 앞 · 맨 끝은 구조물이 얹혀도(병합이 종류를 irregular 로 갈음해도)
|
|
`end:BP/EP` · 구간형 시작 · 끝 = `:start` · `:end` · 서버 `station_keys` = B06 `stationKeysOf`.
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
ROOT = HERE.parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
import B05_Profile.B05_Profile_Structures_Router as router_module # noqa: E402
|
|
import B06_Section.B06_Section_Repository as b06_repo # noqa: E402
|
|
from B05_Profile.B05_Profile_Engine_Sections import cross_filename # noqa: E402
|
|
from B05_Profile.B05_Profile_Route_Save_Parts import station_keys # noqa: E402
|
|
from common_util.common_util_drainage_pipes import pipe_points_path_in # noqa: E402
|
|
|
|
PROJECT_ID = "45245245-2452-4524-5245-245245245245"
|
|
LONG_REL = "B06_Section/longitudinal/longitudinal.json"
|
|
|
|
|
|
def _write_longitudinal(project: Path, stations):
|
|
path = project / LONG_REL
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps({"stations": stations}), encoding="utf-8")
|
|
return path
|
|
|
|
|
|
# ── 433-17① 이관 = 저장 종단 파일 ─────────────────────────────────────────
|
|
|
|
|
|
@pytest.fixture()
|
|
def migrate_client(tmp_path, monkeypatch):
|
|
project = tmp_path / "project"
|
|
|
|
async def fake_root(project_id):
|
|
return str(project)
|
|
|
|
async def fake_length(project_id):
|
|
return 400.0
|
|
|
|
async def fake_latest(connection, project_id):
|
|
return {"id": 7}
|
|
|
|
async def fake_longitudinal(connection, project_id, route_id):
|
|
assert route_id == 7
|
|
return {"longitudinal_file_path": LONG_REL}
|
|
|
|
class _Acquire:
|
|
async def __aenter__(self):
|
|
return object()
|
|
|
|
async def __aexit__(self, *exc):
|
|
return False
|
|
|
|
monkeypatch.setattr(router_module, "_project_root", fake_root)
|
|
monkeypatch.setattr(router_module, "_route_length", fake_length)
|
|
monkeypatch.setattr(router_module, "get_latest_route", fake_latest)
|
|
monkeypatch.setattr(router_module, "get_db_pool", lambda: SimpleNamespace(acquire=_Acquire))
|
|
monkeypatch.setattr(b06_repo, "get_longitudinal_section", fake_longitudinal)
|
|
app = FastAPI()
|
|
app.include_router(router_module.router)
|
|
return TestClient(app), project
|
|
|
|
|
|
def test_초안_측점_본문은_버리고_저장_관_자리는_이관_안_함(migrate_client):
|
|
client, project = migrate_client
|
|
pipes = pipe_points_path_in(project)
|
|
pipes.parent.mkdir(parents=True, exist_ok=True)
|
|
pipes.write_text(
|
|
json.dumps({"route_signature": "s", "points": [{"chainage_m": 351.0, "id": "p1"}]}),
|
|
encoding="utf-8",
|
|
)
|
|
_write_longitudinal(
|
|
project,
|
|
[
|
|
{"chainage_m": 0.0, "kind": "bp"},
|
|
{"chainage_m": 351.0, "kind": "irregular", "structure": "파형강관 D800"},
|
|
{"chainage_m": 400.0, "kind": "ep"},
|
|
],
|
|
)
|
|
before = pipes.read_bytes()
|
|
# 화면이 초안(관 351 → 364) 상세를 들고 와도 — 옛 본문 꼴을 보내 봄
|
|
body = {"route_id": 7, "stations": [{"chainage_m": 364.0, "structure": "낙석방지책"}]}
|
|
response = client.post(f"/api/projects/{PROJECT_ID}/route/structures/migrate", json=body)
|
|
assert response.status_code == 200, response.text
|
|
assert response.json()["migrated"] == 0
|
|
assert not (project / "B05_Profile" / "route" / "structures.json").exists()
|
|
assert pipes.read_bytes() == before
|
|
|
|
|
|
def test_저장_종단의_옛_자유글_측점은_이관(migrate_client):
|
|
client, project = migrate_client
|
|
_write_longitudinal(
|
|
project,
|
|
[
|
|
{"chainage_m": 0.0, "kind": "bp"},
|
|
{"chainage_m": 260.0, "kind": "irregular", "structure": "낙석방지책"},
|
|
{"chainage_m": 400.0, "kind": "ep"},
|
|
],
|
|
)
|
|
response = client.post(
|
|
f"/api/projects/{PROJECT_ID}/route/structures/migrate", json={"route_id": None}
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
assert response.json()["migrated"] == 1
|
|
stored = client.get(f"/api/projects/{PROJECT_ID}/route/structures").json()["structures"]
|
|
assert [(item["type_id"], item["chainage_m"]) for item in stored] == [("etc", 260.0)]
|
|
|
|
|
|
def test_저장_종단이_없으면_아무것도_안_함(migrate_client):
|
|
client, project = migrate_client
|
|
response = client.post(f"/api/projects/{PROJECT_ID}/route/structures/migrate", json={})
|
|
assert response.status_code == 200 and response.json()["migrated"] == 0
|
|
assert not (project / "B05_Profile" / "route" / "structures.json").exists()
|
|
|
|
|
|
# ── 433-10 관 PUT 측점 다시 뜸 = 옛 cross 파일 지움 ─────────────────────────
|
|
|
|
|
|
def test_관_옮김_측점_다시_뜸이_옛_자리_횡단_파일을_지움(tmp_path, monkeypatch):
|
|
import B05_Profile.B05_Profile_Engine_Sections as engine
|
|
import B06_Section.B06_Section_Router_Stations as stations_module
|
|
|
|
_write_longitudinal(
|
|
tmp_path,
|
|
[
|
|
{"chainage_m": 0.0, "kind": "bp"},
|
|
{"chainage_m": 351.0, "kind": "irregular", "structure": "파형강관 D800"},
|
|
{"chainage_m": 400.0, "kind": "ep"},
|
|
],
|
|
)
|
|
cross_dir = tmp_path / "B06_Section" / "cross_sections"
|
|
cross_dir.mkdir(parents=True)
|
|
for at in (0.0, 351.0, 400.0):
|
|
(cross_dir / cross_filename(at)).write_text("{}", encoding="utf-8")
|
|
|
|
def fake_generate(*args, **kwargs):
|
|
(cross_dir / cross_filename(361.0)).write_text("{}", encoding="utf-8")
|
|
return [{"chainage_m": 361.0, "kind": "irregular", "structure": "파형강관 D800"}]
|
|
|
|
async def fake_inputs(project_id, route, snapshot):
|
|
return None, None, {"longitudinal_file_path": LONG_REL}
|
|
|
|
async def fake_run(fn, *args):
|
|
return None # DB 행 맞춤 · 등급 요약은 이 시험 밖
|
|
|
|
monkeypatch.setattr(engine, "_load_route_polyline", lambda *a: [[0.0, 0.0], [400.0, 0.0]])
|
|
monkeypatch.setattr(engine, "_load_pipe_points", lambda *a: [])
|
|
monkeypatch.setattr(engine, "resolve_extra_stations", lambda *a: [])
|
|
monkeypatch.setattr(engine, "generate_irregular_sections", fake_generate)
|
|
monkeypatch.setattr(stations_module, "_generation_inputs", fake_inputs)
|
|
monkeypatch.setattr(stations_module, "run_with_connection", fake_run)
|
|
|
|
made = asyncio.run(
|
|
stations_module._regenerate_structure_stations(
|
|
PROJECT_ID,
|
|
tmp_path,
|
|
{"id": 7, "route_data_path": "route.json"},
|
|
{"filter_key": "f", "method": "m"},
|
|
)
|
|
)
|
|
assert made == 1
|
|
assert sorted(p.name for p in cross_dir.glob("cross_*.json")) == sorted(
|
|
cross_filename(at) for at in (0.0, 361.0, 400.0)
|
|
)
|
|
|
|
|
|
# ── 433-18 시종점 열쇠 한 규칙 ─────────────────────────────────────────────
|
|
|
|
|
|
STRUCTURES = [
|
|
{"structure_id": "w1", "type_id": "retaining_wall", "chainage_m": 80.0, "start_m": 60.0},
|
|
]
|
|
# 구간형 옹벽 끝 = 노선 끝(EP) — 병합이 EP 행 종류를 irregular 로 갈음한 저장분 꼴
|
|
SECTIONS = [
|
|
{"chainage_m": 0.0, "kind": "bp"},
|
|
{"chainage_m": 20.0, "kind": "regular"},
|
|
{"chainage_m": 40.0, "kind": "regular"},
|
|
{"chainage_m": 60.0, "kind": "irregular", "structure": "옹벽"},
|
|
{"chainage_m": 80.0, "kind": "irregular", "structure": "옹벽"},
|
|
{"chainage_m": 100.0, "kind": "irregular", "structure": "옹벽"},
|
|
]
|
|
|
|
|
|
def _structures_ns():
|
|
return [SimpleNamespace(**item, end_m=100.0, options={}) for item in STRUCTURES]
|
|
|
|
|
|
def test_서버_시종점_열쇠가_구조물보다_먼저():
|
|
keys = station_keys(SECTIONS, [], _structures_ns(), 20.0)
|
|
assert keys == {
|
|
"end:BP": 0.0,
|
|
"grid:1": 20.0,
|
|
"grid:2": 40.0,
|
|
"structure:w1:start": 60.0,
|
|
"structure:w1": 80.0,
|
|
"end:EP": 100.0,
|
|
}
|
|
|
|
|
|
def test_B06_열쇠가_서버와_같음(tmp_path):
|
|
work = tmp_path / "b452"
|
|
build = subprocess.run(
|
|
[
|
|
"node",
|
|
str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"),
|
|
"build",
|
|
"--configLoader",
|
|
"runner",
|
|
"--ssr",
|
|
"../resources/tester/helper_452_entry.ts",
|
|
"--outDir",
|
|
str(work),
|
|
"--logLevel",
|
|
"error",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=300,
|
|
)
|
|
assert build.returncode == 0, build.stderr
|
|
bundle = work / "bundle.mjs"
|
|
shutil.copy(work / "helper_452_entry.js", bundle)
|
|
source = work / "input.json"
|
|
source.write_text(
|
|
json.dumps(
|
|
{
|
|
"sections": SECTIONS,
|
|
"structures": [{**item, "end_m": 100.0} for item in STRUCTURES],
|
|
"interval": 20.0,
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
proc = subprocess.run(
|
|
["node", str(HERE / "helper_452_keys.mjs"), str(bundle), str(source)],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=120,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
ts = {entry["key"]: entry["chainageM"] for entry in json.loads(proc.stdout)}
|
|
assert ts == station_keys(SECTIONS, [], _structures_ns(), 20.0)
|
|
|
|
|
|
def test_노선_저장_이월_EP_고침이_새_EP_에_남음():
|
|
from B05_Profile.B05_Profile_Route_Save_Parts import carry_cross_inputs
|
|
|
|
old_keys = station_keys(SECTIONS, [], _structures_ns(), 20.0)
|
|
# 노선이 늘어 옹벽이 따라감 — 끝(EP) 110 · 시작 70 · 기준 90
|
|
moved = [
|
|
SimpleNamespace(
|
|
structure_id="w1",
|
|
type_id="retaining_wall",
|
|
chainage_m=90.0,
|
|
start_m=70.0,
|
|
end_m=110.0,
|
|
options={},
|
|
)
|
|
]
|
|
new_sections = [
|
|
{"chainage_m": at, "kind": "regular"} for at in (0.0, 20.0, 40.0, 60.0, 70.0, 80.0, 90.0)
|
|
] + [{"chainage_m": 100.0, "kind": "regular"}, {"chainage_m": 110.0, "kind": "irregular"}]
|
|
new_keys = station_keys(new_sections, [], moved, 20.0)
|
|
designs = {100.0: {"ground_type": "rock"}, 60.0: {"ground_type": "weathered"}}
|
|
inputs, _, dropped = carry_cross_inputs(old_keys, new_keys, designs, ("ground_type",), ())
|
|
assert inputs[110.0] == {"ground_type": "rock"} # EP 고침 = 새 EP
|
|
assert inputs[70.0] == {"ground_type": "weathered"} # 구간 시작 고침 = 따라간 시작
|
|
assert dropped == 0
|